paul shen
/posts/drawings/photos/about

The three core React hook features

May 27, 2019

In this post, we'll break down the core React component API and how hooks expose this functionality.

React hooks provide an interface to the backing component instance. Before hooks, there was no way for a function component to communicate with its instance. In class components, you had access to instance variables, setState, and lifecycle methods.

There are three fundamental features that React component APIs provide.

  • Read and write instance data
  • Schedule render updates
  • Schedule effects

Let's see how these features were represented in class components and now with React hooks.

Read and write instance data

In class components, instance data was either stored as an instance variable or as part of this.state. There is nothing special about instance variables. Class components are just class instances; you can access this.thing as you please. this.state is just a special instance variable that React is aware of and manages.

With function components, we no longer have access to the component instance (this) so we need another API. This is where useRef comes in. thingRef.current is yours to read and write as you please, just like this.thing in class components.

function MyFunctionComponent() {
  const didMount = useRef(false);
  useEffect(() => {
    didMount.current = true;
  }, []);
  ...
}

class MyClassComponent extends React.Component {
  didMount = false;
  componentDidMount() {
    this.didMount = true;
  }
  ...
}

Schedule a render update

We need a mechanism to trigger rerenders or else we'll left with quite the static application. You could trigger an update by rerendering the root node but that isn't scalable. We need a component API to schedule updates.

In class components, the API was this.setState and this.forceUpdate. Calling these functions added an update to React's internal queue. this.setState is actually scheduling two things at the same time, an update to instance data and a render update.

Hook useState provides the same functionality for function components. More than just scheduling a render update, useState manages instance data and avoids rendering if the new state is equivalent.

function useForceUpdate() {
  const [, setState] = useState(1);
  return () => setState(x => x + 1);
}

function MyComponent() {
  const forceUpdate = useForceUpdate();
  return <div onClick={forceUpdate} />;
}

Schedule effects

Effects are code that change the world outside React. Making a network request, directly mutating a DOM node, and subscribing to Redux updates are effects.

Effect code is written in the class component lifecycle methods componentDidMount, componentDidUpdate, and componentWillUnmount. It's in these methods you access the DOM and mutate the state of the world.

In hook land, we have the reasonably named useEffect. Lifecycle methods have an iOS callback-style API while useEffect reads as a function call, which pushes onto a queue. You can push multiple effects in a single function component render.

useLayoutEffect is another hook for scheduling effects, but synchronous with committing host mutations (DOM updates) like the class lifecycle methods.

Hook flavors

React core provides a few more hooks for the above functionality.

Preserving referential equality is paramount when working in React. useEffect takes an additional argument to decide whether to skip an update. Similarly, PureComponent and React.memo rely on referential equality to decide whether to skip a render update.

To help with this, React provides hooks useMemo and useCallback that return existing values if the same inputs are provided. These fall into the category of reading and writing instance data. In fact, you can implement useMemo and useCallback on top of useRef! I'll leave this as an exercise. (Hint: What are the cached values for a memoize function?)

useState and useReducer both provide access to instance state and a way to schedule render updates. In fact, useState is a tiny wrapper on useReducer!

This list isn't comprehensive though others are usually related cousins. For example, useContext can be categorized with setState, managing state and scheduling render updates across multiple components instead of one.

Recap

React components are simple when you break it down. In addition to rendering, components schedule updates and effects. They also can hold state, whether stored in React state or instance variables.

These were available before in class components but are now exposed in hooks for function components. As the hook ecosystem grows with useful higher-level hooks, remember it's the core functionality and hooks described here that enable all this power!

Browse posts. Get new post updates on twitter.