Hello folks, today I will be talking about useEffect
hook in React.
Previosly I've written about about useState
hook if you want to read that; part 1 and part 2
So, what's useEffect
? In short, we use this hook to tell React that our component needs to do something after each render. React will keep that (useEffect function) in mind, and trigger it after performing DOM updates. This can be used in many scenarios, but today I will use it for fetching data.
Fist, I will explain how it works, then I will use it for fetching data.
This is index.js
component:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.js
component:
import { useEffect } from "react";
// css is from boiler plate (code that comes with React), I didn't code anything for styling
import "./App.css";
function App() {
useEffect(() => {
console.log(`useEffect comes AFTER page render`);
});
console.log(`Page is rendered`);
return (
<div className="App">
<h1> useEffect </h1>
</div>
);
}
export default App;
If you go to console, you should see Page is rendered
on top of useEffect comes AFTER page render
because useEffect runs after each render. Now, this is out of the way, let's talk about dependency list in useEffect
a bit. I will add a counter that only increments the value in App.js
file.
Here's App.js again
:
import { useState, useEffect } from "react";
// css is from boiler plate (code that comes with React); however, I added a bit too :D
import "./App.css";
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
console.log(`useEffect come AFTER page render`);
});
const increment = () => {
setCounter((prevVal) => prevVal + 1);
};
console.log(`page is RENDERED`);
return (
<div className="App">
<h1> useEffect </h1>
<h2> {counter} </h2>
<button onClick={increment} className="btn">
Increment
</button>
</div>
);
}
export default App;
In short, the code above has a button that increments value of counter
. I don't want to be repetitive, so if you've got no clue about useState you can check this. It helps understand how increment
function works
However, if you go to console on the browser, you will notice each time the value changes, useEffect
hook gets triggered.
The reason for that is useEffect
is called after every render except if you add a second argument; depency list. If you want to fire useEffect
only in initial render, then you've got to add an empty array as a second argument like so:
useEffect(() => {
console.log(`ONLY INITIAL RENDER`);
}, []);
As you can see from the image above, it's rendered only once. OK... But what's the use of this thing that renders only once? This will prevent us from falling into an infinite loop when calling a function in useEffect
, and for this example that function will be fetching data.
For now I will use a classic; https://jsonplaceholder.typicode.com.
Time to share the whole code and explain it in small chunks:
import { useState, useEffect } from "react";
// css is from boiler plate (code that comes with React); however, I added a bit too :D
import "./App.css";
function App() {
const [users, setUsers] = useState([]);
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
setUsers((prevUsers) => (prevUsers = users));
console.log(users);
};
useEffect(() => {
fetchUsers();
});
return <div className="App"></div>;
}
export default App;
First, we need to import useEffect
and useState
like so; import { useState, useEffect } from "react";
.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
setUsers((prevUsers) => (prevUsers = users));
console.log(users);
};
The function above should be asynchronous because we can never anticipate when the data will come, and without data we shouldn't be executing lines below (at least not without error handling), so I used async
, await
syntax. As you can see, I fetched data from jsonplaceholder.typicode.com (obviously, used the exact "path" to get data) by using this line of code const response = await fetch("https://jsonplaceholder.typicode.com/users");
. However, to work with that data, I got to convert it to a json
, so I converted it to json like so; await response.json()
. Finally, put that json into a variable called users
.
Note:
Do not confuse users
variable that I use with users
variable that I used in hook (const users
VS. const [users, setUsers]
). Remeber, there's scope in JavaScript. So, users
variable that's inside of fetchUsers
function is not accessible outside of that function.
Finally, the code above sets users
(in fetchUsers function) variable to const [users, setUsers]
hook by this
code setUsers((prevUsers) => (prevUsers = users));
.
Note:
useState
hook automatically receives previous value as argument, and I like being explicit when I updat value of state. So, I explicitly write prevVal = data
when I set new data to it. If it was a counter, I would've written setCounter((prevCount)=> prevCount + 1)
. However,setUsers(users)
would also work.
We could use axios to make fetching easier, but this is a really simple example, so I didn't bother using it. Keeping axios for later
The problem with the code above is that it will drag us down to an infinite loop. Why? Because useEffect
runs after each render. Let me show the code again
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
setUsers((prevUsers) => (prevUsers = users));
console.log(users);
};
useEffect(() => {
fetchUsers();
});
Code above will run fetchUsers
function after initial render. Then, fetchUsers
function will get data, and page will re-render. After that useEffect
will run again. So, data will be fetched again, then useEffect
will run again and so on. To avoid this, we add second argument which is an empty array, so this useEffect
runs only on initial render.
useEffect(() => {
fetchUsers();
}, []);
OK, so we fetched data, now let's use it. It will be an extremely simple usage though, I added this to the bottom of App
component only:
return (
<div className="App">
{users.map((user) => {
return (
<div key={user.id}>
<h2> {user.name} </h2>
</div>
);
})}
</div>
);
The code above, maps over users
array and displays names of users in h2 tag. Logic behind the code above is similar to logic here. If that's not clear, please read this. Other than that, since list rendering is not topic of this blog post, I won't be covering that.
Final code looks like this:
import { useState, useEffect } from "react";
// css is from boiler plate (code that comes with React); however, I added a bit too :D
import "./App.css";
function App() {
const [users, setUsers] = useState([]);
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
setUsers((prevUsers) => (prevUsers = users));
console.log(users);
};
useEffect(() => {
fetchUsers();
}, []);
return (
<div className="App">
{users.map((user) => {
return (
<div key={user.id}>
<h2> {user.name} </h2>
</div>
);
})}
</div>
);
}
export default App;
Alright that was it for today, we briefly talked about useEffect
hook. The source that I used if React documentation by the way.
Top comments (5)
I will definitely look into this, thank you for your feedback!
Great article, keep the good work! Liked and followed! 🚀
Thank you so much, very much appreciated!
Great article thanks to sharing! >.<
Thank you!