How and Why to use React Hooks
March 28, 2020
Why Hooks
React Hooks is a newer, opt-in way to write React code as of version 16.8 that comes with various pre-defined hook methods that serve a variety of functions. Hooks can only be used in functional components and have a syntax that is shorter and easier to read than class-based components. Hooks are optional, and can be converted one at a time when convenient.
If you’ve written any React code in the past, there’s a good chance you’ve made a class component before. They are generally straightforward and simple to use. State is stored in an object using this.setState
and effects are called using lifecycle
methods. These features do not exist in hooks, however they are replaced by similar ones.
The basic hooks we will be using in this tutorial include
- useState
- useEffect
- useContext
There are also additional hooks such as
- useCallback
- useRef
- useReducer
- useMemo
- useLayoutEffect
Importing Hooks
Hooks come included with React 16.8 (and above), so you will not need to install any additional dependencies to get started. Importing these hooks is a similar to how you import React.Component
in class components. For example, you can import the useState
and useEffect
hooks in your React import as follows
Import React, { useState, useEffect } from 'react';
*Alternatively you can use these hooks directly with React.useState()
or React.useEffect()
useState
This hook serves the same purpose as our state
object in class-based components. A basic useState
hook is declared, generally at the top of your functional component’s JavaScript like this
const [data, setData] = useState([]);
Here, we are declaring the variable we want to keep track of data (equivalent to this.state.data
in classes) as well as a setter function setData (similar to this.setState({ data })
). The initial value for data gets passed in as the useState
argument (an empty array in this case).
useState vs this.setState
If you’ve used many class-based components before, you’ll know that this.setState
only changes the properties you specify. For example
state = {
user: "jimmysmith@gmail.com",
data: []
}
If we were to call the following code
this.setState({ data: [1,2,3] });
our data value in state
would be adjusted and the value for user would remain the same. this.setState
only overwrites the specified properties.
The useState
hook is slightly different in its effect. For example if we had the following hook
const [state, setState] = useState({
user: "jimmysmith@gmail.com"
data: []
})
Calling the following method
setState({
data: [1,2,3]
})
would overwrite the user
value jimmysmith@gmail.com in our state hook.
We can avoid this issue by first spreading our hook’s value, then changing our desired properties
setState({
...state,
data: [1,2,3]
})
This will add the user
and data
properties from state, then replace data
with our array.
The useState
hook can take some time to get used to, but once you understand how to implement state using this hook, it will end up saving you complexity in your code. You can either declare useState hooks for each variable, or use one hook to store an object with all of your state data.
useEffect
This hook will try to run each time your application re-renders. It takes two arguments: a function to run, along with an optional array that indicates when the hook should trigger.
On Component Mounting
As a basic example, the following useEffect
hook will have the same effect as componentDidMount
in a class-based component
useEffect(() => {
console.log('functional component mounted');
}, [])
Here the empty array indicates that the effect hook should run on the first render, and not again after that. If we were to leave off the empty array and just provide an arrow function, that function would run on every render.
On Component Updating
Sometimes we will only want to run this effect when one of our components props
updates. In a class-based component this would look similar to
class ClassExample extends React.Component {
componentDidUpdate(prevProps) {
if(prevProps.data !== this.props.data) {
console.log('data prop was updated');
}
}
...
}
Since this prevProps !== this.props
logic is regularly used in componentDidUpdate
, this code is simplified in the following useEffect
hook
const HookExample = ({ data }) {
useEffect(() => {
console.log('data prop was updated');
}, [data])
...
}
Passing data
into the second argument’s array in useEffect
indicates that this hook should be run on any re-render in which the data
property changes. We can include multiple variables in this array, separated by comma, in order to run the same logic when different variables change.
useContext
Context in React has been around for a while, however useContext
simplifies its use in functional components. It allows us to pass down props layered deeply in the component tree, without having to declare them on each individual child component.
To create a context, declare a new variable like this
const DemoContext = React.createContext();
Each context has access to Provider and Consumer wrappers which pass props down your component tree. By wrapping your components in a Provider
and supplying a value property, any component inside that Provider will also be able to read that value by accessing the Consumer
.
const App = () => {
return(
<DemoContext.Provider value={{
color: 'green',
number: 42
}}>
<Page />
</DemoContext.Provider>
)
}
const Page = () => {
return(
<Item />
)
}
const Item = () => {
return(
<div>Item here</div>
)
}
In this example, we have an Item
component nested three levels deep which doesn’t receieve any props from its parent components. If we wanted, we could pass these values down through the props of the Page
and Item
components. The context
hook instead allows us to pull in those values with the useContext
hook.
const Item = () => {
const demo = useContext(DemoContext)
return(
<div style={{color: demo.color}}>Item {demo.number} here</div>
)
}
As long as the DemoContext
we created above is imported in the Item
component’s file, we will have access to all the properties passed down in the provider. Here the consumer
is extracted from the DemoContext
and wrapped around the entire component. useContext
allows us to cleanly consume parent properties without having to wrap our child component in a consumer and use a render props
pattern that is normal for class-based components.
Conclusion
There are lots of other hooks available in React, including custom hooks. This tutorial included some of the most commonly used ones. Overall, there are plenty of benefits and reasons to get writing functional components with Hooks today!