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
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!