Hooks make it possible to organize logic in components, making them tiny and reusable without writing a class. In a sense, they’re React’s way of leaning into functions because, before them, we’d have to write them in a component and, while components have proven to be powerful and functional in and of themselves, they have to render something on the front end. That’s all fine and dandy to some extent, but the result is a DOM that is littered with divs that make it gnarly to dig through through DevTools and debug.
Well, React Hooks change that. Instead of relying on the top-down flow of components or abstracting components in various ways, like higher-order components, we can call and manage flow inside of a component. Dan Abramov explains it well in his Making Sense of React post:
Hooks apply the React philosophy (explicit data flow and composition) inside a component, rather than just between the components. That’s why I feel that Hooks are a natural fit for the React component model.
Unlike patterns like render props or higher-order components, Hooks don’t introduce unnecessary nesting into your component tree. They also don’t suffer from the drawbacks of mixins.
The rest of Dan’s post provides a lot of useful context for why the React team is moving in this direction (they’re now available in React v16.7.0-alpha) and the various problems that hooks are designed to solve. The React docs have an introduction to hooks that, in turn, contains a section on what motivated the team to make them. We’re more concerned with how the heck to use them, so let’s move on to some examples!
The important thing to note as we get started is that there are nine hooks currently available, but we’re going to look at what the React docs call the three basic ones: useState()
, useEffect
, and setContext()
. We’ll dig into each one in this post with a summary of the advanced hooks at the end.
useState()
Defining state with If you’ve worked with React at any level, then you’re probably familiar with how state is generally defined: write a class and use this.state
to initialize a class:
class SomeComponent extends React.component {
constructor(props)
super(props);
this.state = {
name: Barney Stinson // Some property with the default state value
}
}
React hooks allow us to scrap all that class stuff and put the useState()
hook to use instead. Something like this:
import { useState } from 'react';
function SomeComponent() {
const [name, setName] = useState('Barney Stinson'); // Defines state variable (name) and call (setName) -- both of which can be named anything
}
Say what?! That’s it! Notice that we’re working outside of a class. Hooks don’t work inside of a class because they’re used in place of them. We’re using the hook directly in the component:
import { useState } from 'react';
function SomeComponent() {
const [name, setName] = useState('Barney Stinson');
return
<div>
<p>Howdy, {name}</p>
</div>
}
Oh, you want to update the state of name? Let’s add an input and submit button to the output and call setName
to update the default name on submission.
import { useState } from 'react'
function SomeComponent() {
const [input, setValue] = useState("");
const [name, setName] = useState('Barney Stinson');
handleInput = (event) => {
setValue(event.target.value);
}
updateName = (event) => {
event.preventDefault();
setName(input);
setValue("");
}
return (
<div>
<p>Hello, {name}!</p>
<div>
<input type="text" value={input} onChange={handleInput} />
<button onClick={updateName}>Save</button>
</div>
</div>
)
}
Notice something else in this example? We’re constructing two different states (input and name). That’s because the useState()
hook allows managing multiple states in the same component! In this case, input
is the property and setValue
holds the state of the input element, which is called by the handleInput
function then triggers the updateName
function that takes the input value and sets it as the new name
state.
useEffect()
Create side effects with So, defining and setting states is all fine and dandy, but there’s another hook called useEffect()
that can be used to—you guessed it—define and reuse effects directly in a component without the need for a class or the need to use both redundant code for each lifecycle of a method (i.e. componentDidMount
, componentDidUpdate
, and componentWillUnmount
).
When we talk about effects, we’re referring to things like API calls, updates to the DOM, and event listeners, among other things. The React documentation cites examples like data fetching, setting up subscriptions, and changing the DOM as possible use cases for this hook. Perhaps the biggest differentiator from useState()
is that useEffect()
runs after render. Think of it like giving React an instruction to hold onto the function that passes and then make adjustments to the DOM after the render has happened plus any updates after that. Again, the React documentation spells it out nicely:
By default, it runs both after the first render and after every update. […] Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
Right on, so how do we run these effects? Well, we start off by importing the hook the way we did for useState()
.
import { useEffect } from 'react';
In fact, we can call both useState()
and useEffect()
in the same import:
import { useState, useEffect } from 'react';
Or, construct them:
const { useState, useEffect } = React;
So, let’s deviate from our previous name example by hooking into an external API that contains user data using axios inside the useEffect()
hook then renders that data into a list of of users.
First, let’s bring in our hooks and initialize the App.
const { useState, useEffect } = React
const App = () => {
// Hooks and render UI
}
Now, let’s put useState()
to define users
as a variable that contains a state of setUsers
that we’ll pass the user data to once it has been fetched so that it’s ready for render.
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
// Our effects come next
}
Here’s where useEffect()
comes into play. We’re going to use it to connect to an API and fetch data from it, then map that data to variables we can call on render.
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
// Connect to the Random User API using axios
axios("https://randomuser.me/api/?results=10")
// Once we get a response, fetch name, username, email and image data
// and map them to defined variables we can use later.
.then(response =>
response.data.results.map(user => ({
name: `{user.name.first} ${user.name.last}`,
username: `{user.login.username}`,
email: `{user.email}`,
image: `{user.picture.thumbnail}`
}))
)
// Finally, update the `setUsers` state with the fetched data
// so it stores it for use on render
.then(data => {
setUsers(data);
});
}, []);
// The UI to render
}
OK, now let’s render our component!
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
axios("https://randomuser.me/api/?results=10")
.then(response =>
response.data.results.map(user => ({
name: `{user.name.first} ${user.name.last}`,
username: `{user.login.username}`,
email: `{user.email}`,
image: `{user.picture.thumbnail}`
}))
)
.then(data => {
setUsers(data);
});
}, []);
return (
<div className="users">
{users.map(user => (
<div key={user.username} className="users__user">
<img src={user.image} className="users__avatar" />
<div className="users__meta">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
</div>
))}
</div>
)
}
Here’s what that gets us:
It’s worth noting that useEffect()
is capable of so, so, so much more, like chaining effects and triggering them on condition. Plus, there are cases where we need to cleanup after an effect has run—like subscribing to an external resource—to prevent memory leaks. Totally worth running through the detailed explanation of effects with cleanup in the React documentation.
useContext()
Context and Context in React makes it possible to pass props down from a parent component to a child component. This saves you from the hassle of prop drilling. However, you could only make use of context in class components, but now you can make use of context in functional components using useContext()
. Let’s create a counter example, we will pass the state and functions which will be used to increase or decrease the count from the parent component to child component using useContext()
. First, let’s create our context:
const CountContext = React.createContext();
We’ll declare the count state and increase/decrease methods of our counter in our App component and set up the wrapper that will hold the component. We’ll put the context hook to use in the actual counter component in just a bit.
const App = () => {
// Use `useState()` to define a count variable and its state
const [count, setCount] = useState(0);
// Construct a method that increases the current `setCount` variable state by 1 with each click
const increase = () => {
setCount(count + 1);
};
// Construct a method that decreases the current `setCount` variable state by 1 with each click.
const decrease = () => {
setCount(count - 1);
};
// Create a wrapper for the counter component that contains the provider that will supply the context value.
return (
<div>
<CountContext.Provider
// The value is takes the count value and updates when either the increase or decrease methods are triggered.
value={{ count, increase, decrease }}
>
// Call the Counter component we will create next
<Counter />
</CountContext.Provider>
</div>
);
};
Alright, onto the Counter component! useContext()
accepts an object (we’re passing in the CountContext
provider) and allows us to tell React exactly what value we want (`count) and what methods trigger updated values (increase
and decrease
). Then, of course, we’ll round things out by rendering the component, which is called by the App.
const Counter = () => {
const { count, increase, decrease } = useContext(CountContext);
return (
<div className="counter">
<button onClick={decrease}>-</button>
<span className="count">{count}</span>
<button onClick={increase}>+</button>
</div>
);
};
And voilà! Behold our mighty counter with the count powered by context objects and values.
Wrapping up
We’ve merely scratched the surface of what React hooks are capable of doing, but hopefully this gives you a solid foundation. For example, there are even more advanced hooks that are available in addition to the basic ones we covered in this post. Here’s a list of those hooks with the descriptions offered by the documentation so you can level up now that you’re equipped with the basics:
Hook | Description |
---|---|
userReducer() | An alternative to useState . Accepts a reducer of type (state, action) => newState , and returns the current state paired with a dispatch method. |
useCallback() | Returns a memoized callback. Pass an inline callback and an array of inputs. useCallback will return a memoized version of the callback that only changes if one of the inputs has changed. |
useMemo() | Returns a memoized value. Pass a “create” function and an array of inputs. useMemo will only recompute the memoized value when one of the inputs has changed. |
useRef() | useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue ). The returned object will persist for the full lifetime of the component. |
useImperativeMethods | useImperativeMethods customizes the instance value that is exposed to parent components when using ref . As always, imperative code using refs should be avoided in most cases. useImperativeMethods should be used with forwardRef . |
useLayoutEffect | The signature is identical to useEffect , but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint. |
4th embed has missing
const
on handler functions.First example with useEffect is missing (though not needed?) the
$
before the brackets in the mapped response data.Thanks for the article, good beefy read. Tiny typos at the bottom table: userReducer is useReducer, useImperativeMethods is useImperativeHandle
This is not entirely correct.
Components don’t have to render something – they can simply return null or just delegate to one or more (using Fragments) other components without needing a DOM node of their own.
We do see all the wrapper components from higher order components in the react-dev-tools but every level doesn’t contribute a wrapper DOM node (div or otherwise).