CHAPTER 4useEffect
Whenever we need to do things in components that are not strictly the rendering, like fetching data, or subscribing to a chat room, we need useEffect
.
useEffect
registers a function (the "effect") to be executed at certain times when the component re-renders, and declares what values will, if changed, trigger a new execution.
Every time
To execute an effect every time your component renders, just use the first parameter:
console.log(`Component was rendered!`);
useEffect(() => {
console.log(`useEffect was called!`);
});
The first time
To execute an effect just the first time a component renders (to fetch data, for instance), the second parameter has to be an empty array. Here is a component showing a list of random user names from randomuser.me
"use client";
import { useEffect, useState } from "react";
type UserList = Array<Record<string, any>>;
const loadUsers = async () => {
const response = await fetch(`https://randomuser.me/api?results=20`);
const { results: users } = await response.json();
return users;
};
export default function RandomUserList() {
const [userList, setUserList] = useState<UserList>([]);
useEffect(() => {
loadUsers().then(setUserList);
}, []);
return (
<div>
{userList.map(({ name }) => (
<div>
{name.first} {name.last}
</div>
))}
</div>
);
}
Notice two things:
-
We had to pass a type to
useState
to indicate what type is the value we are storing (otherwise, just with the[]
it cannot be deduced). -
We have to call
loadUsers
withoutawait
becauseuseEffect
functions cannot be asynchronous, so we usethen
to set a callback. Also the callback can be directly besetUserList
, since it is expecting, as a first parameter, the data to be kept.
Depending on certain values
In general, a component has two types of data inputs: prop values and state values. Props change because a parent has sent different values from last time. State changes due to events, typically. But those inputs are the ones that are shown on the screen in some way. If any of those change, the component has to re-render.
This is still true if the input values are used in a fetch
returning data that is ultimately shown on the screen, since changing the values will need to re-execute the fetch
to make it "fresh".
Such is the case with search:
"use client";
import { useEffect, useState } from "react";
type BeerList = Array<Record<string, any>>;
const BASE_URL = `https://api.escuelajs.co/api/v1`;
const searchProducts = async (search: string) => {
const response = await fetch(`${BASE_URL}/products?title=${search}`);
const products = await response.json();
console.log(products);
return products;
};
export default function SearchBeers({ search }: { search: string }) {
const [products, setProducts] = useState<BeerList>([]);
useEffect(() => {
searchProducts(search).then(setProducts);
}, [search]);
return (
<div>
{products.map((product) => (
<p key={product.id}>{product.title}</p>
))}
</div>
);
}
This component receives a search
prop, which tell it to show products filtered by that value. To get them, the component has to do a fetch
every time the search
prop changes, and therefore, the search
value is in the array as the second parameter to useEffect
.