The ABCs of React Hooks

React Hooks are like alphabet soup - it's easy as A, B, C!

Posted on December 22, 2021

Blog readers, it's come to my attention that far too many devs get mucked down in new languages, syntax, tools, and endless engineering debates while skipping a critical lesson: learning the very basic concepts. So here is my remedy (or attempt) at correct that problem, at least for React hooks. Through a handful of lessons, I'm going to teach you how to write super clean, super cool React Hooks using TypeScript. But again, we're going to start with the basics, and you can't get much more basic than the ABC's.

So here's where we'll start: Describing all the React Hooks using the ABC's.

What's A React Hook?

Before getting to the ABC's of React Hooks, I probably should first define what a React Hook is:

I'll do my version first:

a React Hook is a function that takes input and returns output

And for completeness, React's official description:

Hooks let you use state and other React features without writing a class.

(and yes, the official documentation capitalizes the 'H' in 'Hooks', so I'll be following their lead.)

That's it.

Seems too simple right? I'll prove it, explaining each React Hook with our beloved ABC's.

useState In ABC's:

useState just has to be the first hook we mention, right? It's React's entire bread and butter; the very things that cause components to re-render and are the foundation of our UIs in the first place! So let's start with it.

Give me a value A and setter function B, where I want my initial value of A to be C

const [A, B] = useState(C)

useEffect In ABC's:

Of course, we need to have side effects as well. Sometimes, when a state variable changes, we want to do something not directly affecting our markup or UI, but something additional like call an API or trigger a different state change somewhere else. How can we do this in a functional way? The answer is useEffect. It's a way to run a function when something changes. Let's add more letters to our alphabet soup, this time a function D, that will be run when something changes:

Run function D when E changes

useEffect(() => {
   D()
}, [E])

but we of course note useEffect() seems to have an array for the "when E changes" part, so let's imagine we have also a value F we want to detect changes in. Well, simply add this to the change array:

Run function D when E or F changes

useEffect(() => {
   D()
}, [E, F])

Want to run function D when nothing changes? That's fine too, just empty out your dependency array. The change here is then implicitly interpreted by React, in this case that the body of the hook will only fire once - when the hook mounts. This is the equivalent of the (now infamous) componentDidMount:

Run function D on mount

useEffect(() => {
    D()
}, [])

So, you may be asking, well if there is a hook equivalent for componentDidMount, there certainly should be one for componentWillUnmount, right? Yep! This is done by using return with a function in useEffect. Let's add more letters to our alphabet soup, this time a function G, that will be run when this hook unmounts:

Run function G on unmount

useEffect(() => {
    return () => G()
}, [])

So far so good, right? To tell you the truth, you can tackle 90% of application-like logic that you may need with just the useState and useEffect Hooks. But there are a few more hooks that are worth knowing, so let's continue.

useRef In ABC's:

Sometimes we need to store a value that we want to persist across renders, but we don't want to trigger a re-render when that value changes. This is where useRef comes in. It's a way to store a value that is not part of the state of the component, and it's a way to store a value that will not trigger a re-render when it changes.

Give me a value S, where I want my initial value of H to be I

const H = useRef(I)

useContext In ABC's:

We all know the pain and mess that can be created by passing props too deeply or in an overly complicated manner in our React apps. This is where useContext comes in. It allows you to pass a value from a parent component to a child component without having to pass it through all the components in between. Simply create a context, we'll call it J, with property K. Then, in the child component, you can use useContext to get the value from the context. Alternatively, you may want to consider using Redux, which can give you a more global state management solution, where useSelector would be used to select the desired value from the Redux store.

Create a context J with property K, where I want my initial value of K to be "hello world!"

const J = createContext({ K: "hello world!" })

Child component:

Give me the property K from the context J

const { K } = useContext(J)
console.log(K) // "hello world!"

useReducer In ABC's:

Like useContext, you may not need to use this hook very often, if at all, if you are using Redux, but for completeness, here it is.

Give me a value L and dispatcher function M, where reducer function N uses initial argument O

const [L, M] = useReducer(N, O)

useMemo In ABC's:

Sometimes we have a function that is computationally expensive, and we don't want to re-run it every time the component re-renders. This is where useMemo comes in. It's a way to store a value that is not part of the state of the component, and it's a way to store a value that will not trigger a re-render when it changes - it will only re-run when the dependencies change.

Give me a value P as a result of function Q, which uses variable R, and I want to re-calculate memoized value P with function Q every time R changes

const P = useMemo(func Q() { 
    // do something computationally expensive based on variable R (slow)
}, [R])

useCallback In ABC's:

Now, let's take a closer look at the useCallback hook. This hook is particularly useful for memoizing functions, ensuring that a function reference remains constant across renders unless its dependencies change. It's similar to useMemo, but would be used in the case where for example you are passing an entire function as a prop to a component as apposed to just a value:

Give me a memoized function S, which is a result of function T, which uses variable U, and I want to re-memoize function T every time U changes

const S = useCallback(T, [U])

You may notice that useMemo and useCallback are very similar. The difference between them is that useMemo returns a memoized value, while useCallback returns a memoized function. So, if you want to memoize a function, use useCallback, and if you want to memoize a value returned by a function, use useMemo!

Thanks!

I hope this post helped you start to wrap you mind. When you abstract the actual business logic and actual functions that might be used in a React app, you can see that the React Hooks are actually not too hard to get a hang of. Again, just like any other function in JavaScript, they have some sort of input and provide output. And, they are just functions that run when something changes. That's it. 😉

Cheers!

-Chris

Next / Previous Post:

Find more posts by tag:

-~{/* */}~-