To handle side effects in React, I use the useEffect
hook, which is designed for these situations.
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlacesByLocation = sortPlacesByDistance(
AVAILABLE_PLACES,
position.coords.latitude,
position.coords.longitude
);
setSortedPlaces(sortedPlacesByLocation);
});
}, []);
In the example above, useEffect
accepts two arguments:
- A function β‘οΈ this is the effect you want to run.
- An array of dependencies β‘οΈ these determine when the effect runs again.
Essentially, useEffect runs the provided function after the component has been rendered. This trigger a re-render, which can be problematic if your component is complex or you have multiple effects.
Since the dependency array in this example is empty ([])
, the effect runs only onceβafter the initial render. If the array had dependencies, the effect would re-run whenever those dependencies changed. Dependencies can include functions, state, context values, and more.
useEffect(() => {
const timer = setTimeout(() => {
onConfirm();
}, 3000);
return () => {
clearTimeout(timer);
};
}, [onConfirm]);
In this case, the effect confirms a deletion modal action after 3 seconds. Unlike the previous example, this one includes a dependency: onConfirm
wich is a prop that leads to a function. If onConfirm
changes, the effect will re-run.
Note
JavaScript functions are objects, so even two equal functions are not considered equal when compared.
To prevent infinite re-renders caused by function recreations, I use the useCallback hook. Here's how:
const handleRemovePlace = useCallback(() => {
setPickedPlaces((prevPickedPlaces) =>
prevPickedPlaces.filter((place) => place.id !== selectedPlace.current)
);
setIsModalOpen(false);
const placesId = JSON.parse(localStorage.getItem("savedPlaces")) || [];
localStorage.setItem(
"savedPlaces",
JSON.stringify(placesId.filter((id) => id !== selectedPlace.current))
);
}, []);
useCallback
is similar to useEffect
in that it accepts a function and a dependency array. However, it memoizes the function, storing it in React's internal memory. This prevents the function from being recreated unnecessarily, which is particularly helpful when itβs used as a dependency in useEffect or other hooks.
Finally, useEffect supports cleanup functions, which are essential when working with intervals, timeouts, or subscriptions. For example:
useEffect(() => {
const interval = setInterval(() => {
setDeletingTime((prevTime) => prevTime - 100);
}, 100);
return () => {
clearInterval(interval);
};
}, []);
The cleanup function runs either when the component is unmounted or before the effect re-runs. This ensures that we donβt leave unnecessary intervals or subscriptions running in the background, which could cause performance issues.
πΈ This project is a practice exercise I learned from the Academind's React Course πΈ