Hooks are not a new concept in React - they are a re-implementation (a new API) for state and side effects in React that better aligns with two existing concepts in react: the concept of Components as the primitive model for UIs, and of these components being declarative.
Let's look at the concept of Components as the UI Primitives first. Before hooks, Components are a mental idea that don't map directly to either of the two existing implementations: the class-based or function-based. While the mental model was right, the implementation was forced. And so you’d have to sometimes switch between them.
What’s going on is there is a missing primitive for components: a single Component implementation which is like stateful functions with effects - and that is what hooks enable.
Before looking at how Hooks solves for this, let’s look at the other concept hooks are better aligned with: Using declarative code to make components easier to reason about.
React components have always allowed declarative code to be mapped to an imperative DOM. In the case of functional components, this declarative approach included not just the render, but the whole component (b/c the whole component was just a render): a mapping of data to an object describing the UI. React would take this object and surgically (imperatively) update the DOM.
However, if a component needed local state or side-effects - it had to be implemented as a class components. And while the render function was still declarative, the class instance itself (where the state lived and side-effects were managed) was mutable. State and side-effects were tied to a mutating instance, which made them harder to reason about.
The react team wanted the single missing component primitive to itself be declarative (as functional components were), even when it included state and side-effects.
Hooks provide for this missing component primitive. They allow components to be truly declarative even if they contain state and side-effects. They are a re-conception and re-implemenation of state and side-effects in React - an implementation instead of in class components, is in functional components making use of "hooks".
"Ok, Yeah, cool, whatever.. So what are hooks?"
Hooks are functions used with functional components that let you "hook into" React state and perform side-effects (as previously done with lifecycle hooks).
React provides built-in Hooks, and these can even be used to build more advanced custom hooks.
By convention hook functions are prefixed with “use”.
While hooks are “just functions”, they are not your father’s functions... They do not behave like normal functions. We'll come back to that.
The useState
hook for managing local state
Rather than the state living on a mutable this
instance of the component (this.state
and this.setState
), state is declaratively retrieved from the useState
hook.
State is now retrieved and set declaratively without mutating the structure of the component (ie as the class instance would be).
The highlighted line shows the useState
hook. The value passed is the initial value. The hook returns an array for two items, state and a setter for that state, and we destructure them to variables count and setCount.
The useEffect
hook for managing side-effects
Rather than side-effects being aligned with the component's mutation (componentDidMount
, componentDidUpdate
, componentWillUnmount
), they are now declaratively aligned with state using the useEffect
hook.
useEffect
orients the side effect (and it’s clean-up) with the state, rather than component’s mutation.
The highlighted line shows the useEffect
hook. We pass in a function that performs some side-effect, and the state that that effect is coupled with. Whenever that state changes, the effect function is run.
"But hold on.. WTF.. wouldn't these hooks be reset every render?"
"The hooks are created inside the functional component which are called for every render. Looking back up at the useState
example, wouldn't const [count, setCount] = useState(0);
be called every render and keep reseting the count to the initial value of 0?"
It would seem that way, if useState
was a typical function - but it's not. Hooks are impure* functions - but that impurity is an implementation detail in React that is abstracted away from userland code.
*They are impure as a consequence of JS being the host language, which does not support Algebraic Effects.
An example using hooks
Here is a contrived example using the useState
and useEffect
hooks, vs using class components and lifecycle methods.
Here is live code of the hooks version (on the left): https://codesandbox.io/s/greeting-hooks-2uwdg
Here is live code of the class component version (on the right):
https://codesandbox.io/s/greeting-classes-7bmql
Notice how in the hook version, state and effects are kept together.
A second example using hooks
Lets look at a second example of using hooks. Here are two versions of a contrived component which lets you search for a user, and edit their name.
Here is the hooks version: https://codesandbox.io/s/react-hooks-are-a-better-mental-model-f9kql
Here is the class version: https://codesandbox.io/s/react-classes-are-the-wrong-mental-model-n9zbs
Notice how, again, the state and effects are kept together with hooks - but more-so this time that a bug is avoided which is in the class component version. ("Save" a user, and while it is saving change the users name. The confirmation message will confirm the wrong name - the newly updated one rather than the one which was saved. This is because by the time the save side-effect finishes, the state is mutated. With hooks, state is functionally-scoped and closed-over, and each render introduces new immutable state.)
Custom Hooks add the missing primitive for state/effect sharing.
Now that we've got a grasp on hooks - how functional components using hooks are a new UI primitive which make state and side-effects easier to reasonable through a declarative API - there is one more important note: beyond just co-locating the state and side-effects, these can be abstracted out into a custom re-useable hook. Hooks represent a primitive for state/effect sharing, as Components are a primitive for UI sharing. Building custom Hooks allow for extracting component logic into reusable functions.
Looking back at our first example. We can build a custom useWidth
hook that extracts the width state and effect. Then that hook can be re-used by any component!
Here is live code showing the above: https://codesandbox.io/s/greeting-hooks-as-a-primative-xb0o0
At first glance, it may look like this code-sharing could have been achieved by making a Width
component. But that gets at the heart of it: we don't want a re-useable UI primitive, we want a re-useable state/effect primitive. If it were a UI primitive, the state and effects would be tied to a UI representation - we just want the data, so it can be presented however different components decide.
What other built-in Hooks are there?
Basic Hooks
- useState
- useEffect
- useContext
Additional Hooks
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
More resources
Introducing Hooks (reactjs.org)
Making Sense of React Hooks (medium.com)
A Complete Guide to useEffect (overreacted.io)
Thinking in React Hooks (wattenberger.com)
Individual Photos of Class vs Hooks code with and without Highlighting
Classes: https://thepracticaldev.s3.amazonaws.com/i/9t0sa5bfzos9nh8yfumy.png
Classes Highlighted: https://thepracticaldev.s3.amazonaws.com/i/ah9b8plpz32jejub7nfl.png
Hooks: https://thepracticaldev.s3.amazonaws.com/i/gupc51cvr005gnkuzriu.png
Hooks Highlighted: https://thepracticaldev.s3.amazonaws.com/i/p0tr7pzokmlovbm1v3bw.png
Together:
https://thepracticaldev.s3.amazonaws.com/i/8kt6btrmwqpa1mnffzi9.png
Together Highlighted:
https://thepracticaldev.s3.amazonaws.com/i/bqk4xi68eidx7jmwq6ca.png
Top comments (1)
Why a class component instance is not declarative?