Hooks

Hooks is a new concept that allows you to compose state and side effects. They allow you to reuse stateful logic between components.

If you've worked with Preact for a while, you may be familiar with patterns like "render props" and "higher order components" that try to solve these challenges. These solutions have tended to make code harder to follow and more abstract. The hooks API makes it possible to neatly extract the logic for state and side effects, and also simplifies unit testing that logic independently from the components that rely on it.

Hooks can be used in any component, and avoid many pitfalls of the this keyword relied on by the class components API. Instead of accessing properties from the component instance, hooks rely on closures. This makes them value-bound and eliminates a number of stale data problems that can occur when dealing with asynchronous state updates.

There are two ways to import hooks: from preact/hooks or preact/compat.



Introduction

The easiest way to understand hooks is to compare them to equivalent class-based Components.

We'll use a simple counter component as our example, which renders a number and a button that increases it by one:

class Counter extends Component {
  state = {
    value: 0
  };

  increment = () => {
    this.setState(prev => ({ value: prev.value +1 }));
  };

  render(props, state) {
    return (
      <div>
        Counter: {state.value}
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}Run in REPL

Now, here's an equivalent function component built with hooks:

function Counter() {
  const [value, setValue] = useState(0);
  const increment = useCallback(() => {
    setValue(value + 1);
  }, [value]);

  return (
    <div>
      Counter: {value}
      <button onClick={increment}>Increment</button>
    </div>
  );
}Run in REPL

At this point they seem pretty similar, however we can further simplify the hooks version.

Let's extract the counter logic into a custom hook, making it easily reusable across components:

function useCounter() {
  const [value, setValue] = useState(0);
  const increment = useCallback(() => {
    setValue(value + 1);
  }, [value]);
  return { value, increment };
}

// First counter
function CounterA() {
  const { value, increment } = useCounter();
  return (
    <div>
      Counter A: {value}
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// Second counter which renders a different output.
function CounterB() {
  const { value, increment } = useCounter();
  return (
    <div>
      <h1>Counter B: {value}</h1>
      <p>I'm a nice counter</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}Run in REPL

Note that both CounterA and CounterB are completely independent of each other. They both use the useCounter() custom hook, but each has its own instance of that hook's associated state.

Thinking this looks a strange? You're not alone!

It took many of us a while to grow accustomed to this approach.

The dependency argument

Many hooks accept an argument that can be used to limit when a hook should be updated. Preact inspects each value in a dependency array and checks to see if it has changed since the last time a hook was called.

In our useCounter() implementation above, we passed an array of dependencies to useCallback():

function useCounter() {
  const [value, setValue] = useState(0);
  const increment = useCallback(() => {
    setValue(value + 1);
  }, [value]);  // <-- the dependency array
  return { value, increment };
}Run in REPL

Passing value here causes useCallback to return a new function reference whenever value changes. This is necessary in order to avoid "stale closures", where the callback would always reference the first render's value variable from when it was created, causing increment to always set a value of 1.

This creates a new increment callback every time value changes. For performance reasons, it's often better to use a callback to update state values rather than retaining the current value using dependencies.

Stateful hooks

Here we'll see how we can introduce stateful logic into functional components.

Prior to the introduction of hooks, class components were required anywhere state was needed.

useState

This hook accepts an argument, this will be the initial state. When invoking this hook returns an array of two variables. The first being the current state and the second one being the setter for our state.

Our setter behaves similar to the setter of our classic state. It accepts a value or a function with the currentState as argument.

When you call the setter and the state is different, it will trigger a rerender starting from the component where that useState has been used.

import { h } from 'preact';
import { useState } from 'preact/hooks';

const Counter = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  // You can also pass a callback to the setter
  const decrement = () => setCount((currentCount) => currentCount - 1);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  )
}Run in REPL

When our initial state is expensive it's better to pass a function instead of a value.

useReducer

The useReducer hook has a close resemblance to redux. Compared to useState it's easier to use when you have complex state logic where the next state depends on the previous one.

const initialState = 0;
const reducer = (state, action) => {
  switch (action) {
    case 'increment': return state + 1;
    case 'decrement': return state - 1;
    case 'reset': return 0;
    default: throw new Error('Unexpected action');
  }
};

function Counter() {
  // Returns the current state and a dispatch function to
  // trigger an action
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      {count}
      <button onClick={() => dispatch('increment')}>+1</button>
      <button onClick={() => dispatch('decrement')}>-1</button>
      <button onClick={() => dispatch('reset')}>reset</button>
    </div>
  );
}Run in REPL

Memoization

In UI programming there is often some state or result that's expensive to calculate. Memoization can cache the results of that calculation allowing it to be reused when the same input is used.

useMemo

With the useMemo hook we can memoize the results of that computation and only recalculate it when one of the dependencies changes.

const memoized = useMemo(
  () => expensive(a, b),
  // Only re-run the expensive function when any of these
  // dependencies change
  [a, b]
);Run in REPL

Don't run any effectful code inside useMemo. Side-effects belong in useEffect.

useCallback

The useCallback hook can be used to ensure that the returned function will remain referentially equal for as long as no dependencies have changed. This can be used to optimize updates of child components when they rely on referential equality to skip updates (e.g. shouldComponentUpdate).

const onClick = useCallback(
  () => console.log(a, b),
  [a, b]
);Run in REPL

Fun fact: useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

useRef

To get a reference to a DOM node inside a functional components there is the useRef hook. It works similar to createRef.

function Foo() {
  // Initialize useRef with an initial value of `null`
  const input = useRef(null);
  const onClick = () => input.current && input.current.focus();

  return (
    <>
      <input ref={input} />
      <button onClick={onClick}>Focus input</button>
    </>
  );
}Run in REPL

Be careful not to confuse useRef with createRef.

useContext

To access context in a functional component we can use the useContext hook, without any higher-order or wrapper components. The first argument must be the context object that's created from a createContext call.

const Theme = createContext('light');

function DisplayTheme() {
  const theme = useContext(Theme);
  return <p>Active theme: {theme}</p>;
}

// ...later
function App() {
  return (
    <Theme.Provider value="light">
      <OtherComponent>
        <DisplayTheme />
      </OtherComponent>
    </Theme.Provider>
  )
}Run in REPL

Side-Effects

Side-Effects are at the heart of many modern Apps. Whether you want to fetch some data from an API or trigger an effect on the document, you'll find that the useEffect fits nearly all your needs. It's one of the main advantages of the hooks API, that it reshapes your mind into thinking in effects instead of a component's lifecycle.

useEffect

As the name implies, useEffect is the main way to trigger various side-effects. You can even return a cleanup function from your effect one if needed.

useEffect(() => {
  // Trigger your effect
  return () => {
    // Optional: Any cleanup code
  };
}, []);Run in REPL

We'll start with a Title component which should reflect the title to the document, so that we can see it in the address bar of our tab in our browser.

function PageTitle(props) {
  useEffect(() => {
    document.title = props.title;
  }, [props.title]);

  return <h1>{props.title}</h1>;
}Run in REPL

The first argument to useEffect is an argument-less callback that triggers the effect. In our case we only want to trigger it, when the title really has changed. There'd be no point in updating it when it stayed the same. That's why we're using the second argument to specify our dependency-array.

But sometimes we have a more complex use case. Think of a component which needs to subscribe to some data when it mounts and needs to unsubscribe when it unmounts. This can be accomplished with useEffect too. To run any cleanup code we just need to return a function in our callback.

// Component that will always display the current window width
function WindowWidth(props) {
  const [width, setWidth] = useState(0);

  function onResize() {
    setWidth(window.innerWidth);
  }

  useEffect(() => {
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  return <div>Window width: {width}</div>;
}Run in REPL

The cleanup function is optional. If you don't need to run any cleanup code, you don't need to return anything in the callback that's passed to useEffect.

useLayoutEffect

The signature is identical to useEffect, but it will fire as soon as the component is diffed and the browser has a chance to paint.

useErrorBoundary

Whenever a child component throws an error you can use this hook to catch it and display a custom error UI to the user.

// error = The error that was caught or `undefined` if nothing errored.
// resetError = Call this function to mark an error as resolved. It's
//   up to your app to decide what that means and if it is possible
//   to recover from errors.
const [error, resetError] = useErrorBoundary();Run in REPL

For monitoring purposes it's often incredibly useful to notify a service of any errors. For that we can leverage an optional callback and pass that as the first argument to useErrorBoundary.

const [error] = useErrorBoundary(error => callMyApi(error.message));

A full usage example may look like this:

const App = props => {
  const [error, resetError] = useErrorBoundary(
    error => callMyApi(error.message)
  );

  // Display a nice error message
  if (error) {
    return (
      <div>
        <p>{error.message}</p>
        <button onClick={resetError}>Try again</button>
      </div>
    );
  } else {
    return <div>{props.children}</div>
  }
};Run in REPL

If you've been using the class based component API in the past, then this hook is essentially an alternative to the componentDidCatch lifecycle method. This hook is was introduced with Preact 10.2.0 .

Built by a bunch of lovely people like @k1r0s.