Best Practices With React Hooks
Best Practices With React Hooks
Adeneye David Abiodun2020-04-15T11:00:00+00:002020-04-16T11:07:14+00:00
React Hooks are a new addition in React 16.8 that let you use state and other React features without writing a class
component. In other words, Hooks are functions that let you “hook into” React state and lifecycle features from function components. (They do not work inside class
components.)
React provides a few built-in Hooks like useState
. You can also create your own Hooks to reuse stateful behavior between different components. The example below shows a counter whose state is managed using the useState()
hook. Each time you click on the button, we make use of setCount()
to update the value of count
by 1
.
This example renders a counter with a value of 0
. When you click the button, it increments the value by 1
. The initial value of the component is defined using useState
.
const [count, setCount] = useState(0)
As you can see, we set that to be 0
. Then we use the onClick()
method to call setCount
when we want to increment the value.
<button onClick={() => setCount(count + 1)}>
Click me
</button>
Before the release of React Hooks, this example would have used more lines of code, as we’d have had to make use of a class
component.
Rules Of React Hooks
Before we dive deep into the best practices, we need to understand the rules of React Hooks which are also some of the fundamental concepts of the practices presented in this article.
React Hooks are JavaScript functions, but you need to follow two rules when using them.
Note: These two rules were introduced in React Hooks, as opposed to being part of JavaScript itself.
Let’s look at these rules in more detail.
Call Hooks At The Top Level
Don’t call Hooks inside loops, conditions, or nested functions. Always use Hooks at the top level of your React function. 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
and useEffect
calls.
Let’s make a Form
component which will have two states:
accountName
accountDetail
These states will have default values, we’ll make use of the useEffect
hook to persist the state to either the local storage of our browser or to the title of our document.
Now, this component will be maybe to successfully manage its state if it remains the same between multiple calls of useState
and useEffect
.
function Form() {
// 1. Use the accountName state variable
const [accountName, setAccountName] = useState('David');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', accountName);
});
// 3. Use the accountDetail state variable
const [accountDetail, setAccountDetail] = useState('Active');
// 4. Use an effect for updating the title
useEffect(function updateStatus() {
document.title = accountName + ' ' + accountDetail;
});
// ...
}
If the order of our Hooks changes (which can be possible when they are called in loops or conditionals), React will have a hard time figuring out how to preserve the state of our component.
// ------------
useState('David') // 1. Initialize the accountName state variable with 'David'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Active') // 3. Initialize the accountdetail state variable with 'Active'
useEffect(updateStatus) // 4. Add an effect for updating the status
// -------------
// Second render
// -------------
useState('David') // 1. Read the accountName state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Active') // 3. Read the accountDetail state variable (argument is ignored)
useEffect(updateStatus) // 4. Replace the effect for updating the status
// ...
That’s the order React follows to call our hooks. Since the order remains the same, it will be able to preserve the state of our component. But what happens if we put a Hook call inside a condition?
// 🔴 We're breaking the first rule by using a Hook in a condition
if (accountName !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', accountName);
});
}
The accountName !== ''
condition is true
on the first render, so we run this Hook. However, on the next render the user might clear the form, making the condition false
. Now that we skip this Hook during rendering, the order of the Hook calls becomes different:
useState('David') // 1. Read the accountName state variable (argument is ignored)
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Active') // 🔴 2 (but was 3). Fail to read the accountDetails state variable
useEffect(updateStatus) // 🔴 3 (but was 4). Fail to replace the effect
React wouldn’t know what to return for the second useState
Hook call. React expected that the second Hook call in this component corresponds to the persistForm
effect, just like during the previous render — but it doesn’t anymore. From that point on, every next Hook
call after the one we skipped would also shift by one — leading to bugs.
This is why Hooks must be called on the top level of our components. If we want to run an effect conditionally, we can put that condition inside our Hook.
Note: Check out the React Hook docs to read more on this topic.
Only Call Hooks From React Components
Don’t call Hooks from regular JavaScript functions. Instead, you can call Hooks from React function components. Let’s take look at the difference between JavaScript function and React component below:
JavaScript Function
import { useState } = "react";
function toCelsius(fahrenheit) {
const [name, setName] = useState("David");
return (5/9) * (fahrenheit-32);
}
document.getElementById("demo").innerHTML = toCelsius;
Here we import the useState
hook from the React package, and then declared our function. But this is invalid as it is not a React component.
React Function
import React, { useState} from "react";
import ReactDOM from "react-dom";
function Account(props) {
const [name, setName] = useState("David");
return <p>Hello, {name}! The price is <b>{props.total}</b> and the total amount is <b>{props.amount}</b></p>
}
ReactDom.render(
<Account total={20} amount={5000} />,
document.getElementById('root')
);
Even though the body of both looks similar, the latter becomes a component when we import React into the file. This is what makes it possible for us to use things like JSX and React hooks inside.
If you happened to import your preferred hook without importing React (which makes it a regular function), you will not be able to make use of the Hook you’ve imported as the Hook is accessible only in React component.
Call Hooks From Custom Hooks
A custom Hook is a JavaScript function whose name starts with use
and that may call other Hooks. For example, useUserName
is used below a custom Hook that calls the useState
and useEffect
hooks. It fetches data from an API, loops through the data, and calls setIsPresent()
if the specific username it received is present in the API data.
export default function useUserName(userName) {
const [isPresent, setIsPresent] = useState(false);
useEffect(() => {
const data = MockedApi.fetchData();
data.then((res) => {
res.forEach((e) => {
if (e.name === userName) {
setIsPresent(true);
}
});
});
});
return isPresent;
}
We can then go on to reuse the functionality of this hook in other places where we need such in our application. In such places, except when needed, we don’t have to call useState
or useEffect
anymore.
By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.
ESLint Plugin
ESLint plugin called eslint-plugin-react-hooks
enforces the rules above. This comes in handy in enforcing the rules when working on a project. I suggest you make use of this plugin when working on your project, especially when working with others. You can add this plugin to your project if you’d like to try it:
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
This plugin is included by default in Create React App. So you don’t need to add it if you bootstrap your React applications using Create-React-App.
Thinking In Hooks
Let’s take a brief look at class
components and functional components (with Hooks), before diving into the few Hooks best practices.
The simplest way to define a component in React is to write a JavaScript function that returns a React element:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
The Welcome
component accepts props
which is an object that contains data and returns a React element. We can then import and render this component in another component.
The class
component uses a programming methodology called Encapsulation which basically means that everything relevant to the class component will live within it. Life-cycle methods (constructors
, componentDidMount()
, render
, and so on) give components a predictable structure.
Encapsulation is one of the fundamentals of OOP (Object-Oriented Programming). It refers to the bundling of data within the methods that operate on that data, and is used to hide the values or state of a structured data object inside a class — preventing unauthorized parties’ direct access to them.
With Hooks, the composition of a component changes from being a combination of life-cycle Hooks — to functionalities with some render at the end.
Function Component
The example below shows how custom Hooks can be used in a functional component (without showcasing what the body is). However, what it does or can do is not limited. It could be instantiating state variables, consuming contexts, subscribing the component to various side effects — or all of the above if you’re using a custom hook!
function {
useHook{...};
useHook{...};
useHook{...};
return (
...
);
}
Class Component
A class
component requires you to extend from React.Component
and create a render
function which returns a React element. This requires more code but will also give you some benefits.
class {
constructor(props) {...}
componentDidMount() {...}
componentWillUnmount() {...}
render() {...}
}
There are some benefits you get by using functional components in React:
- It will get easier to separate container and presentational components because you need to think more about your component’s state if you don’t have access to
setState()
in your component. - Functional components are much easier to read and test because they are plain JavaScript functions without state or lifecycle-hooks.
- You end up with less code.
- The React team mentioned that there may be a performance boost for functional components in future React versions.
This leads to the first best practice when using React Hooks.
Hooks Best Practices
1. Simplify Your Hooks
Keeping React Hooks simple will give you the power to effectively control and manipulate what goes on in a component throughout its lifetime. Avoid writing custom Hooks as much as possible; you can inline a useState()
or useEffect()
instead of creating your own hook.
If you find yourself making use of a bunch of custom Hooks that are related in functionality, you can create a custom hook that acts as a wrapper for these. Let’s take a look at two different functional components with hooks below.
Functional Component v1
function {
useHook(...);
useHook(...);
useHook(...);
return(
<div>...</div>
);
}
Functional Component v2
function {
useCustomHook(...);
useHook(...);
useHook(...);
return(
<div>...</div>
);
}
v2 is a better version because it keeps the hook simple and all other useHook
s are inline accordingly. This allows us to create functionality that can be reused across different components and also gives us more power to control and manipulate our components effectively. Instead of adopting v1 in which our components are littered with Hooks, you should make use of v2 which will make debugging easy and your code cleaner.
2. Organize And Structure Your Hooks
One of the advantages of React Hooks is the ability to write less code that is easy to read. In some cases, the amount of useEffect()
and useState()
can still be confusing. When you keep your component organized it will help in readability and keep the flow of your components consistent and predictable. If your custom Hooks are too complicated, you can always break them down to sub-custom Hooks. Extract the logic of your component to custom Hooks to make your code readable.
3. Use React Hooks Snippets
React Hooks Snippets is a Visual Studio Code extension to make React Hooks easier and faster. Currently, five hooks are supported:
useState()
useEffect()
useContext()
useCallback()
useMemo()
Other snippets have also been added. I have tried working with these Hooks and it has been one of the best practices I’ve personally used while working with them.
There are two ways you can add React Hooks snippets to your project:
- Command
Launch the VS Code Quick open (Ctrl+P), pasteext install ALDuncanson.react-hooks-snippets
and press Enter. - Extension Marketplace
Launch ‘VS Code Extension Marketplace’ (Ctrl+Shift+X) and search for ‘React Hook Snippets’. Then, look for the ‘Alduncanson’ icon.
I recommend the first snippet. Read more about the snippets here or check for the lastest Hooks snippets here.
4. Put Hooks Rules Into Consideration
Endeavor to always put the two rules of Hooks we learned earlier into consideration while working with React Hooks.
- Only call your Hooks at the top level. Don’t call Hooks inside loops, conditions or nested functions.
- Always call Hooks from React function components or from custom Hooks, don’t call Hooks from regular JavaScript functions.
The ESlint plugin called eslint-plugin-react-hooks
enforces these two rules, you can add this plugin to your project if you’d like it as we explain above in rules of hooks section.
Best practices have not been fully resolved because Hooks are still relatively new. So adoption should be taken with precaution one would take in adopting in any early technology. With that in mind, Hooks are the way for the future of React.
Conclusion
I hope you enjoyed this tutorial. We’ve learned the two most important rules of React Hooks and how to effectively think in Hooks. We looked at functional components and some best practices in writing Hooks the right and effective way. As brief as the rules are, it’s important to make them your guiding compass when writing rules. If you are prone to forget it, you can make use of the ESLint plugin to enforce it.
I hope you will take all of the lessons learned here in your next React project. Good luck!
Resources
- “Introducing Hooks,” React Docs
- “Functional vs Class-Components In React,” David Jöch, Medium
- “Mixins Considered Harmful,” Dan Abramov, React Blog
- “React Hooks: Best Practices And A Shift In Mindset,” Bryan Manuele, Medium
- “React Hooks Snippets For VS Code,” Anthony Davis, Visual Code Marketplace