React Hooks

Seth Massarsky
5 min readMay 2, 2021

I started learning React Hooks this week, so lets go over that in this week’s post. Just to say it up front, I’ve really only been looking at these for a few days, so please don’t use this as a reference. That said, the flow isn’t that much different from React components, so I’m getting used to it pretty quickly. If you’re looking to read up on hooks, the documentation on the React website is quite reader friendly so I’d suggest to start there.

React hooks allow you to utilize equivalents of state and component lifecycle methods in functional components. In doing so, they create cleaner and more reusable code that is easier to understand. The two main hooks are useState and useEffect. In addition to these, I’ve also started working with useReducer, a hook that allows you to set up something similar to a Redux store.

useState

useState allows you to handle state in a functional component (something previously only available in class components). As with all hooks, they can be imported:

import React, { useState } from 'react'

or called from React:

import React from 'react'
React.useState('hi but from state')

useState returns an array with a state variable and a method to update state. This return can be destructured and assigned as follows:

const [ state, setState ] = useState('hi but from state')

From here, the value of state (‘hi but from state’) can be referenced by the state variable, and can be reassigned by using the setState method:

const [ state, setState ] = useState('hi but from state')
console.log(state) => 'hi but from state'
setState('goodbye but from state')
console.log(state) => 'goodbye but from state'

Note you can call these whatever you want:

const [ greeting, setGreeting ] = useState('hi but from state')

Unlike class components, the data stored in state does not have to be an object (we stored a string above, and numbers, arrays, objects are all ok). You can also create multiple state variables:

const [ greeting, setGreeting ] = useState('hi but from state')
const [ aNumber, setANumber ] = useState(1)
const [ anObject, setAnObject ] = useState({
greeting: 'hi but from state',
farewell: 'goodbye but from state'
})

Unlike class component this.setState, useState's setter function does not merge new and old state if using objects, so you’ll need to spread prevState:

const [ anObject, setAnObject ] = useState({
greeting: 'hi but from state',
farewell: 'goodbye but from state'
})
setAnObject({ greeting: 'hello!'})
console.log(anObject)
=> { greeting: 'hello!' }
setAnObject((prevState) => {
return {
...prevState,
farewell: 'goodbye but from state'
}
}
console.log(anObject)
=> { greeting: 'hello!', farewell: 'goodbye but from state' }

So yeah, long story short: useState lets you use state in functional components!

useEffect

useEffect allows you to use several component lifecycle methods in functional components. It can be thought of as a combination of componentDidMount, componentDidUpdate and componentWillUnmount all wrapped up in one. useEffect has two main functions, one that sets something up and another(potentially) that cleans up after. Unless opted out of(discussed later), the clean up and then setup functions will be run every time the component re-renders. I’m going to have trouble describing, so lets look at fake code:

const TotallyRealComponent = props => {
useEffect(() => {
subscribeToSomething()
return unsubscribeFromThatThing()
})
}

When TotallyRealComponent mounts, subscribeToSomething will run. From here, every time TotallyRealComponent renders, unsubscribeFromThatThing will run, followed by subscribeToSomething. Finally, when the component unmounts, unsubscribeFromThatThing will be called a final time.

This behavior can be opted out of in two ways utilizing useEffect's second argument. If passed an array as the second argument, useEffect will compare arrays on re-renders and run if there is a difference in result. Lets start with the first way: you can pass an empty array as the second argument. This will make it so that subscribeToSomething runs once when the component mounts, and won’t run unsubscribeFromThatThing until the component will unmount:

const TotallyRealComponent = props => {
useEffect(() => {
subscribeToSomething()
return unsubscribeFromThatThing()
}, []) // game changer here
}

The second way is to pass a variable or variables into the array as the second argument. Lets use the friendId example since we’re using Facebook’s framework:

const TotallyRealComponent = ({ friendId })=> {
useEffect(() => {
subscribeToSomething(friendId)
return unsubscribeFromThatThing(friendId)
}, [friendId]) // game changer here
}

If on re-render the friendId is the same as the previous render, useEffect will skip the clean up and mount functions. If they are different, unsubscribeFromThatThing will run with the previous friendId, and then subscribeToSomething will run with the new friendId. Finally, when the component unmounts, unsubscribeFromThatThing will run with the last subscribed friendId.

Last, since useEffect is just a function, it can be called multiple times in the same component. This can be beneficial for separating areas of concern. Lets say you want to fetch something on mount, and incorporate the previous subscribe / unsubscribe functionality in a class component. Your componentDidMount is starting to share functionality of separate concerns:

componentDidMount() {
fetch(someUrl)
.then(doSomething)
subscribeToSomething()
}
componentDidUpdate() {
if (this.props.friendId !== prevProps.friendId) {
unsubscribeFromThatThing(prevProps.friendId)
subscribeToSomething(this.props.friendId)
}
}
componentWillUnmount() {
unsubscribeFromThatThing(this.props.friendId)
}

While this example isn’t super noticable, if you imagine there being much more going on, you’ll start to have separate concerns grouped together, and similar concerns spread around your component. Lets look at this with hooks:

const TotallyRealComponent = ({ friendId })=> {
useEffect(() => {
fetch(someUrl)
.then(doSomething)
}, [])
useEffect(() => {
subscribeToSomething(friendId)
return unsubscribeFromThatThing(friendId)
}, [friendId]) // game changer here
}

In this case, TotallyRealComponent calls useEffect twice, separating the initial fetch on component mount, and the ongoing management of the friend subscription. Much cleaner and easier to understand at a glance.

useReducer

Last, useReducer! useReducer allows you to, more or less, manage a more complex state using a reducer like you would with Redux. useReducer returns a store and a dispatch function (sound familiar?), and accepts a reducer function and initial state:

const [ store, dispatch ] = useReducer(reducer, initialState)

Similar to useState, the store variable can be referenced to pull values from the store, and the dispatch function can be used to update the store. Dispatch and the store work nearly identically to Redux, so I’m not going to go too much into those here. The only real difference is that useReducer is setting initial state — you don’t need to specify state having a default value in the reducer (you do need to define initialState for useReducer, though):

// don't do this
function reducer(state = {}, action) {
// do this
function reducer(state, action) {

So yeah, that’s pretty much my understanding of the basics of hooks. So far I’ve been trying to implement these in all of my components, and I’m liking it quite a lot. I really like the separation of concerns that useEffect brings, and useState and useReducer feel so much cleaner than state and Redux. Looking forward to utilizing them more!

Thanks for reading!

--

--