React Hooks is a new feature that is likely coming in React 16.7. If you have been on Twitter, you have likely seen the amount of buzz surrounding the announcement of React Hooks. A quick look at Google Trends for the past 30 days, reveals that there has been an increased interest in React hooks.
So what are React Hooks and how can you use them in your React code base? We will consider this in this article.
To get the most out of this tutorial, you need knowledge of JavaScript and the React framework. If you want to play around with React Hooks, you will need the alpha build of React as this feature is still in alpha (as at the time of writing this article).
When you are creating a React application, you usually have to create components. Components are the building block of React applications. React components can be either stateful or stateless.
When you create a component as a class that extends the React.Component
class, you have access to state and React lifecycle methods: componentDidUpdate
, componentDidMount
, and the others.
This is a typical React component. It has access to state and lifecycle methods:
1import React from 'react'; 2 3 export default class extends React.Component { 4 state = { 5 name: 'Neo Ighodaro' 6 } 7 8 componentDidUpdate() { 9 // [...] 10 11 this.setState({ 12 name: 'Neo Not Ighodaro' 13 }) 14 15 // [...] 16 } 17 }
However, when you create stateless components, you don’t have access to state of lifecycle hooks by default.
1import React from 'react' 2 3 export default () => { 4 return ( 5 <div className="island"> 6 <h1>Hi, I'm stateless :(</h1> 7 </div> 8 ) 9 }
This is where React Hooks come in. React Hooks allows you to access state and lifecycle hooks in a React function component.
Note: React hooks are still in alpha and should probably not be used in a production environment as the API could still change before it reaches the stable build. You can follow the RFC here. You can also look at the documentation and FAQs here.
The React team has stated specifically that they do not intend to replace classes as the Facebook team uses them extensively. Hooks are just another arrow in the quiver of tools available. If anything, Hooks can make it easier to break down complex React components as seen in Dan Abramov’s ReactConf 2018 demo code presentation.
With React Hooks, you can extract complex components and break them down into smaller functions that are a lot more testable.
Note: At the time of writing, the hooks feature was still in alpha so some of the API might have changed in the stable version of React.
Now that we have talked about what React Hooks are, let’s see some of the Hooks that come bundled with React and how we can use them in our React applications.
Let’s look at a typical React component example:
1import React from "react"; 2 import ReactDOM from "react-dom"; 3 4 import "./styles.css"; 5 6 export class DoStuffButton extends React.Component { 7 constructor(props) { 8 super(props); 9 this.state = { buttonText: "Do something" }; 10 this.updateButtonText = this.updateButtonText.bind(this); 11 } 12 13 updateButtonText() { 14 this.setState({ buttonText: "Loading..." }); 15 window.setTimeout( 16 () => this.setState({ buttonText: "Do something" }), 17 2000 18 ); 19 } 20 21 render() { 22 return ( 23 <button onClick={this.updateButtonText}>{this.state.buttonText}</button> 24 ); 25 } 26 } 27 28 export class App extends React.Component { 29 render() { 30 return ( 31 <div className="App"> 32 <DoStuffButton /> 33 </div> 34 ); 35 } 36 } 37 38 const rootElement = document.getElementById("root"); 39 ReactDOM.render(<App />, rootElement);
In the code above, we created a DoStuffButton
component that changes the text when clicked using the state. Then we use the App
component to render it. Simple stuff. Let’s see though how we can simplify the entire code with React hooks; useState
specifically.
1import React, { useState } from "react"; 2 import ReactDOM from "react-dom"; 3 4 import "./styles.css"; 5 6 function DoStuffButton() { 7 const [buttonText, setButtonText] = useState("Do something"); 8 9 function updateButtonText() { 10 setButtonText("Loading..."); 11 window.setTimeout(() => setButtonText("Do something"), 2000); 12 } 13 14 return <button onClick={updateButtonText}>{buttonText}</button>; 15 } 16 17 function App() { 18 return ( 19 <div className="App"> 20 <DoStuffButton /> 21 </div> 22 ); 23 } 24 25 const rootElement = document.getElementById("root"); 26 ReactDOM.render(<App />, rootElement);
While the example is not very practical, it does show how you can use the useState
hook in a React function component.
The useState
function returns an array with two elements. The first item being the current state value and the second being a function used to update the state. Logging the function to the console, we see this:
You can read more about the useState
hook in the documentation here. However, let’s see how useState
can be used in code. We will use the same code as we used in the useState
example with the slight addition of the componentDidMount
lifecycle method:
1import React from "react"; 2 import ReactDOM from "react-dom"; 3 4 import "./styles.css"; 5 6 export class DoStuffButton extends React.Component { 7 constructor(props) { 8 super(props); 9 this.state = { buttonText: "Do something" }; 10 this.updateButtonText = this.updateButtonText.bind(this); 11 } 12 13 // Added this... 14 componentDidMount() { 15 this.setState({ buttonText: 'Set this instead'}) 16 } 17 18 updateButtonText() { 19 this.setState({ buttonText: "Loading..." }); 20 window.setTimeout( 21 () => this.setState({ buttonText: "Do something" }), 22 2000 23 ); 24 } 25 26 render() { 27 return ( 28 <button onClick={this.updateButtonText}>{this.state.buttonText}</button> 29 ); 30 } 31 } 32 33 export class App extends React.Component { 34 render() { 35 return ( 36 <div className="App"> 37 <DoStuffButton /> 38 </div> 39 ); 40 } 41 } 42 43 const rootElement = document.getElementById("root"); 44 ReactDOM.render(<App />, rootElement);
Above, we added the componentDidMount
lifecycle method to the DoStuffButton
component. This method, as you probably know, will be fired when the component is mounted. So how can React hooks provide us this functionality in a React function component? The useEffect
hook. Let’s see this in action:
1import React, { useState, useEffect } from "react"; 2 import ReactDOM from "react-dom"; 3 4 import "./styles.css"; 5 6 function DoStuffButton() { 7 const [buttonText, setButtonText] = useState("Do something"); 8 9 useEffect(() => setButtonText('Set this instead')) 10 11 function updateButtonText() { 12 setButtonText("Loading..."); 13 window.setTimeout(() => setButtonText("Do something"), 2000); 14 } 15 16 return <button onClick={updateButtonText}>{buttonText}</button>; 17 } 18 19 function App() { 20 return ( 21 <div className="App"> 22 <DoStuffButton /> 23 </div> 24 ); 25 } 26 27 const rootElement = document.getElementById("root"); 28 ReactDOM.render(<App />, rootElement);
As seen in the example above, we have implemented the useEffect
hook and it is running when the DoStuffButton
component is mounted.
As said in the docs, “you can think of useEffect
Hook as componentDidMount
, componentDidUpdate
, and componentWillUnmount
combined.”
As with most things in life, there are some rules to consider when using hooks in your React application. The rules are as follows:
By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple
useState
anduseEffect
calls. - Rules of Hooks
By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code. - Rules of Hooks
use
keyword as a prefix to the name of the hook. This makes it easy to follow convention and makes it easy to spot Hooks when looking at the code.As a side note, there is an ESLint plugin that enforces these rules. To try it, you can run the following command in your terminal inside the project directory:
$ npm install eslint-plugin-react-hooks@next
1// Your ESLint configuration 2 { 3 "plugins": [ 4 // ... 5 "react-hooks" 6 ], 7 "rules": { 8 // ... 9 "react-hooks/rules-of-hooks": "error" 10 } 11 }
The plugin will be added to the Create React App and other similar toolkits by default in the future.
One more exciting thing about Hooks is custom Hooks. You can create custom Hooks that can be used all around your application. This will reduce duplicated code by a lot when used properly. Custom functions are regular JavaScript functions that have a name prefixed with the use
keyword and have access to React Hooks.
For instance, we can have a custom hook defined like this:
1// File: FriendStatus.js 2 import { useState } from "react"; 3 4 export function useFriendStatus({ initialState }) { 5 const [friends, setFriends] = useState(initialState); 6 7 const setOnlineStatus = (id, friend, online) => { 8 if (friend.id === id) friend.online = !!online; 9 return friend; 10 }; 11 12 const setFriendOnline = id => { 13 setFriends(friends.map(friend => setOnlineStatus(id, friend, true))); 14 }; 15 16 const setFriendOffline = id => { 17 setFriends(friends.map(friend => setOnlineStatus(id, friend, false))); 18 }; 19 20 return [friends, { setFriendOnline, setFriendOffline }]; 21 } 22 23 24 // File: index.js 25 import React, { useState } from "react"; 26 import ReactDOM from "react-dom"; 27 28 import { useFriendStatus } from "./FriendStatus"; 29 30 import "./styles.css"; 31 32 function FriendListItem() { 33 const [friends, { setFriendOnline, setFriendOffline }] = useFriendStatus({ 34 initialState: [ 35 { id: 1, name: "Phoebe", online: false }, 36 { id: 2, name: "Rachel", online: false }, 37 { id: 3, name: "Ross", online: false } 38 ] 39 }); 40 41 return ( 42 <> 43 {friends.map(friend => ( 44 <li style={{ color: friend.online ? "green" : "grey" }} key={friend.id}> 45 {friend.name} 46 <button onClick={() => setFriendOnline(friend.id)}>Set Online</button> 47 <button onClick={() => setFriendOffline(friend.id)}> 48 Set Offline 49 </button> 50 </li> 51 ))} 52 </> 53 ); 54 } 55 56 const rootElement = document.getElementById("root"); 57 ReactDOM.render(<FriendListItem />, rootElement);
Above we have a simple example of how custom hooks can be used in an application. In the FriendStatus.js
we defined the custom hook and in the index.js
file, we used the custom hooks and the return value to make up the user interface.
In this tutorial, we have learned how to use the new React Hooks feature and how it can improve the readability and testability of complex React components. The introduction of React Hooks should not be seen as a reason to stop using classes but as a tool in your bag to make even better apps.
The source code to the code snippets in this tutorial is available on GitHub.