DEV Community

Jacob Paris for Devcord

Posted on • Updated on

React Hooks: UseEffect, UseCallback, UseMemo

React ships with a whole bunch of hooks that can be a bit tough to grasp when you're learning them all at once. This post should help you understand the differences and use-cases of three of them.

UseEffect

A pure component only interacts with itself and its children. Any time you need to interact with the world outside your component, you are dealing with side-effects.

React gives us a handy hook for dealing with these. the React.useEffect hook lets us specify a function that deals with external forces, provide a second function to clean up after it, and drop a list of dependencies so we can re-run the effect when one of the dependencies change.

Examples of useEffect

Updating the page title

This effect will run the first time the component is rendered, and then only ever run again if the title has changed.

const [title, setTitle] = React.useState("Hooks 101");

React.useEffect(() => {
    document.title = title;
}, [title]);
Enter fullscreen mode Exit fullscreen mode

Fetching data from an API into local state.

Since our state changing will not affect the list of products that is returned, we can pass an empty array [] as our dependency so that the effect will only run when the component is first mounted.

const [products, setProducts] = React.useState([]);

React.useEffect(() => {
    getProducts()
    .then(products => {
        setProducts(products);
    })
}, []);
Enter fullscreen mode Exit fullscreen mode

Fetching data from an API into local state, based on a query.

If we have a query or filter to modify the set of API data we want, then we can pass it as a dependency to make sure that React runs this effect every time the component renders using a new query.

const [products, setProducts] = React.useState([]);
const [query, setQuery] = React.useState("");

React.useEffect(() => {
    getProducts({name: query})
    .then(products => {
        setProducts(products);
    })
}, [query]);
Enter fullscreen mode Exit fullscreen mode

Dispatching a Redux action.

If your GET action already reduces into your Redux state, then you don't need to maintain any of that locally.

By passing products.length as a dependency, you only run this

const dispatch = Redux.useDispatch();
const products = Redux.useSelector(state => state.products);

React.useEffect(() => {
    dispatch(GetProducts())
}, []);
Enter fullscreen mode Exit fullscreen mode

UseMemo

Unlike useEffect, React.useMemo does not trigger every time you change one of its dependencies.

A memoized function will first check to see if the dependencies have changed since the last render. If so, it executes the function and returns the result. If false, it simply returns the cached result from the last execution.

This is good for expensive operations like transforming API data or doing major calculations that you don't want to be re-doing unnecessarily

Example of useMemo

const posts = Redux.useSelector(state => state.posts);

const tags = React.useMemo(() => {
    return getTagsFromPosts(posts)
}, [posts]);
Enter fullscreen mode Exit fullscreen mode

UseCallback

This is a special case for memoizing functions. Since javascript compares equality by reference, the function you create the first time a component renders will be different than the one created in subsequent renders.

If you try passing a function as props or state, this means that it will be treated as a prop change every single time. By wrapping it in useCallback, React will know that it's the same function. You can still add a dependency array to trigger a recalculation if the dependencies change.

A strong use-case here to avoid child component re-renders

Example of useCallback

Every time this component renders, it will also trigger a whole re-render of the Button component because the removeFromCart function is unique every time.

const dispatch = useDispatch();

const removeFromCart = () => dispatch(removeItem(product.id));

return (
    <Button onClick={removeFromCart}>Delete</Button>
);
Enter fullscreen mode Exit fullscreen mode

Replacing our callback with this will avoid that problem entirely. Now the Button will only re-render when our product ID changes, so that it will function to remove the new product from our cart.

const removeFromCart = React.useCallback(() => {
    dispatch(removeItem(product.id))
}, [product.id]);
Enter fullscreen mode Exit fullscreen mode

Further Reading

https://overreacted.io/a-complete-guide-to-useeffect/

https://medium.com/@vcarl/everything-you-need-to-know-about-react-hooks-8f680dfd4349

https://kentcdodds.com/blog/usememo-and-usecallback

https://www.robinwieruch.de/react-hooks-fetch-data/

https://stackoverflow.com/questions/54371244/what-is-the-intention-of-using-reacts-usecallback-hook-in-place-of-useeffect

https://stackoverflow.com/questions/54963248/whats-the-difference-between-usecallback-and-usememo-in-practice/54965033#54965033

Oldest comments (7)

Collapse
 
baily__case profile image
Baily Case

Should add useReducer in there as well since you mentioned Redux. It's great for making predictable state management with the regular React library

Collapse
 
seanmclem profile image
Seanmclem

For useCallback, would we benefit from passing the product.id into the function? instead of accessing the variable from outside the function

Collapse
 
jacobmparis profile image
Jacob Paris

Not really. You could do that:

const removeFromCart = (id) => dispatch(removeItem(id));

return (
    <Button onClick={() => removeFromCart(product.id)}>Delete</Button>
);

but then we might as well go all the way

return (
    <Button onClick={() => dispatch(removeItem(product.id))}>Delete</Button>
);

And then it's apparent we're at the same position of accessing the ID from outside the function call. I like to define them outside of the JSX, and then when we add the useCallback function it stays nice and clean and separated

Collapse
 
seanmclem profile image
Seanmclem

But it's pure

Thread Thread
 
jacobmparis profile image
Jacob Paris

PURE

return (
    <Button
        productId={product.id}
        onClick={e => (
            dispatch(removeItem(e.target.getAttribute("productId")))
        )}
    >Delete</Button>
);
Collapse
 
nisevi profile image
Nicolas Sebastian Vidal

Here are a couple of links from the course I'm taking on FrontendMasters that might be of help too:

Collapse
 
jacobmparis profile image
Jacob Paris

Appreciate it, I've heard Frontend Masters is an excellent course