See how to use the useEffect hook to run code when a state or prop value changes in our React app.
React comes with a few basic hooks to let us build components that work dynamically. Hooks are functions that return values and can be used in components to return different things when reactive values change.
In this article, we will look at how to use the useEffect
hook.
We use the useEffect
hook to do things when any reactive values change. Reactive values include state and prop values.
We can use useEffect
to run code when some state or prop value changes.
For instance, we write:
import { useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>increment</button>
</div>
);
}
to create the count
state with the useState
hook.
And then we call the useEffect
hook to log the value of count
when count
changes. Then we add a button that increments the value of count
by 1 when we click on it. The callback will be called whenever count
changes since we put count
in the array in the second argument of useEffect
.
Whatever state and prop value we put in there will be watched by useEffect
, and the callback in the first argument will be called whenever those values change.
In the callback, we call useState
with a function that takes the previous count
value as the argument and returns the new value of count
derived from that.
By calling setCount
with a function that returns the new state value based on the previous state, we can ensure that we always get the latest state value based on the previous value.
We can make the useEffect
callback only run when the component mounts by calling the useEffect
callback with an empty array.
And we can make the useEffect
hook callback run whenever the component is rerendered by omitting the second argument when calling useEffect
.
We can run cleanup code in the useEffect
when the component unmounts. To do this, we return a function that runs the cleanup code when the component unmounts.
For example, we write:
import { useEffect, useState } from "react";
export default function App() {
const [screenSize, setScreenSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
const { width, height } = screenSize;
const onResize = () => {
setScreenSize({
width: window.innerWidth,
height: window.innerHeight
});
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div>
{width}x{height}
</div>
);
}
to call useEffect
with a function that calls window.addEventListener
to add the onResize
function as the resize
event listener function.
onResize
will then be called when we resize the screen.
And to remove the event listener when the component unmounts, we call window.removeEventListener
to remove the resize
event listener when the App
component unmounts.
The function that is returned in the useEffect
callback is run when we unmount the component.
In onResize
, we call setScreenSize
to set screenSize
to the screen’s width and height, so we see the width
and height
changes when we resize the screen.
Being able to call run code when the component mounts and unmounts with the useEffect
makes it handy for synchronizing with external systems.
We can also manipulate external systems when a reactive value changes with the useEffect
hook.
For example, we write:
import { useEffect, useRef, useState } from "react";
const ModalDialog = ({ open, children }) => {
const ref = useRef();
useEffect(() => {
const dialog = ref.current;
if (open) {
dialog.showModal();
}
return () => {
dialog.close();
};
}, [open]);
return <dialog ref={ref}>{children}</dialog>;
};
export default function App() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open Dialog</button>
<ModalDialog open={open}>
<p>hello world</p>
<button onClick={() => setOpen(false)}>Close Dialog</button>
</ModalDialog>
</div>
);
}
to create the ModalDialog
component.
It takes the open
prop and calls useEffect
that calls dialog.showModal
to open the dialog box when the open
state is true.
And when the component unmounts, we call dialog.close
to close the dialog.
The ref
is assigned as the dialog element’s ref
prop value, so ref.current
is the dialog element.
The useEffect
hook is one of the basic hooks that comes with React. Therefore, we can use it to create our own custom React hooks to encapsulate logic that we will reuse.
To do this, we just have to return the reactive values we want to return so they can be used in components. And the custom hook will just call other hooks to get the values we want to return.
For instance, we can clean up the previous example by moving the resize listener code into its own hook.
To do this, we write:
import { useEffect, useState } from "react";
const useScreenSize = () => {
const [screenSize, setScreenSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
const onResize = () => {
setScreenSize({
width: window.innerWidth,
height: window.innerHeight
});
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return screenSize;
};
export default function App() {
const { width, height } = useScreenSize();
return (
<div>
{width}x{height}
</div>
);
}
We create the useScreenSize
hook that has the logic that we had previously in the App
component. The useState
and useEffect
calls are moved into useScreenSize
and they all do the same thing.
And we return screenSize
so we can use the returned value in any component or another custom hook.
Then in App
, we call useScreenSize
to return the object with the width
and height
. And we render these values in the div.
The useEffect
hook lets us run code that reacts to reactive property values. It is mainly used for running code that interacts with external systems with reactive values in the component changes.
We can make the useEffect
callback run when reactive value changes or when the component mounts or run whenever the component rerenders.
Also, we can return a function in the useEffect
to run any code that we want to run when the component unmounts. This is handy for running any cleanup code when a component unmounts.
John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.