A Guide to Using the useState and useEffect Hooks in React
ADVERTISEMENT
Table of Contents
Introduction
React is a popular frontend JavaScript library for building user interfaces. If you are looking to become a web developer, learning React and React hooks will level up your skills and help you find a job.
React hooks are the modern way to deal with application state and lifecycle events within components. Since React 16.8, hooks have allowed developers to make their React code cleaner and more concise.
In this article, we'll discuss two of the most important hooks used in almost every React component, the useState
and useEffect
hooks.
If you would like to follow along and try out some of the code examples, you can create a new React app by typing npx create-react-app hooks-tutorial
into your terminal. Be sure to have NodeJS installed for that command to work.
Overview
Prior to the introduction of hooks, React components were written as JavaScript classes. In class components, each component stores all of its state variables in a state
property, and state is updated using the setState
function.
Component lifecycle events are handled using methods like componentDidMount()
, shouldComponentUpdate()
, componentDidUpdate()
, and componentWillUnmount()
. Props are passed to the component through its constructor()
function and the component is rendered in a render()
function.
The use of so many methods to create a simple React component was once a major drawback when using the library. Although class components are still a supported feature in React, most developers have opted to use functional components instead since their release in February of 2019.
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidUpdate() {
//...
}
componentDidMount() {
//...
}
componentWillUnmount() {
//...
}
render() {
return (
<h1>
This is such an inefficient way to make components. There must be a
better way!
</h1>
);
}
}
Functional components simplify the development process by allowing each component to be created with just a single function. This function can take in props as arguments and returns JSX instead of using a separate render()
function. React hooks allow functional components to manage state and the component lifecycle in a clean and concise manner.
function App(props) {
return <h1>Now this is the right way to do components!</h1>;
}
useState Hook React
The useState
hook allows us to create state variables for our component. State variables are used to store dynamic data in our component which can change as a user interacts with it. One example of state would be the contents of a form that the user is filling out. As they interact with the form fields, the component is continuously updating its state and re-rendering in order to keep the form data up to date.
Here is an example of useState
in action:
import { useState } from "react";
function App() {
const [input, setInput] = useState("");
return (
<div className="App">
<h1>Input value: {input}</h1>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</div>
);
}
useState
takes in an initial value as an argument and returns an array containing the state variable and a function to mutate it. It is common practice to de-structure this array and set its contents to be const
. This is because the state variable should never be reassigned directly and should only be modified via the setter function. The setter function accepts either a new value or a function which takes the current value as an argument and returns the new value.
useState with Arrays
Consider the following component which builds upon our previous example.
function App() {
const [input, setInput] = useState("");
const [words, setWords] = useState([]);
return (
<div className="App">
<h1>Input value: {input}</h1>
<input
type="text"
placeholder="type something"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button
onClick={() =>
setWords((state) => {
state.push(input);
return state;
})
}
>
Add Word
</button>
{words.map((word, idx) => (
<div key={idx}>{word}</div>
))}
</div>
);
}
This component renders a list of words which can be added-to by typing a word into the input field and pressing the button. However, this code fails to re-render the component when the button is pressed because it doesn't provide an entirely new array object to the setter function.
The correct approach is to use the spread operator to provide a new array object which includes all of the current elements and adds the new element to the end of the array.
<button onClick={() => setWords((state) => [...state, input])}>Add Word</button>
useEffect Hook React
The useEffect
hook allows us to respond to changes in the component lifecycle. The component lifecycle refers to a set of events that occur from the time a component is mounted to the DOM until it is removed. useEffect
is most commonly used to execute code when the component is initially rendered, when it is updated, and when it is unmounted.
The following code adds to our previous example by using useEffect
to change the document title when a new word is added to the list.
function App() {
const [input, setInput] = useState("");
const [words, setWords] = useState([]);
useEffect(() => {
document.title = `${words.length} words`;
}, [words]);
return (
// ...
useEffect
accepts a function and a dependency array as arguments. The function will be executed when a variable in the dependency array changes. If no dependency array is provided, the function will run every time the component is re-rendered. If the dependency array is empty, the function will only be run when the component first mounts to the DOM. A common use case for an empty dependency array would be when fetching data from an API.
function App() {
const [data, setData] = useState(null);
useEffect(async () => {
const res = await fetch("https://api.com/api/v1");
const json = await res.json();
setData(json);
}, []);
return <p>data: {JSON.stringify(data)}</p>;
}
When this component first renders, it will fetch data from the API and display it. The useEffect
function has an empty dependency array because it only needs to fetch the data once when the component first mounts. It doesn't need to rerun in response to any variables changing.
useEffect
can also run a function when the component unmounts, often called a clean up function. This is useful when using setInterval
or event based libraries to clear an interval or an event listener that was set during the component lifecycle. The cleanup function is returned by the function passed into useEffect
as shown below:
function KeyLogger() {
function handleKeyDown(e) {
document.title = `${e.code} pressed`;
}
useEffect(() => {
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
});
return <h2>Press a key</h2>;
}
When this component mounts, it will listen for the keydown
event and display the key being pressed in the document title. The cleanup function removes the event listener when the component unmounts. Without it, the document title would keep updating even after the component unmounted.
When using useEffect
to edit the DOM, there are times when you may want to use useLayoutEffect instead which runs synchronously before the component is rendered.
Summary
In this article, you learned about two important React hooks that are almost always used when writing functional components.
The useState
hook is used for storing variables that are part of your application's state and will change as the user interacts with your website.
The useEffect
hook allows components to react to lifecycle events such as mounting to the DOM, re-rendering, and unmounting.
If you are learning React, you should make sure to learn these hooks well as they will prove to be extremely useful when developing React applications.
Next Steps
To further your knowledge of React hooks, I would suggest looking at useRef and useContext next.
To learn to way to build a backend for your React apps, you may want to check out this Express tutorial.
If you're interested in learning more about the basics of coding, programming, and software development, check out our Coding Essentials Guidebook for Developers, where we cover the essential languages, concepts, and tools that you'll need to become a professional developer.
Thanks and happy coding! We hope you enjoyed this article. If you have any questions or comments, feel free to reach out to jacob@initialcommit.io.
Final Notes
Recommended product: Coding Essentials Guidebook for Developers