React hooks have revolutionized the way developers manage state and side effects in functional components. In this article, we will delve into two fundamental hooks:
useState
anduseEffect
. By the end of this article, you will not only understand the basics of these hooks but also gain insights into when to use them and how to navigate potential pitfalls.The objective is that by the end of reading this article, we understand each for
useState
anduseEffect
:useState
:useState
hook? :At its core, the
useState
hook is a function provided by React that allows functional components to manage state. It enables you to declare state variables in your components without the need for className components.
import React, { useState } from 'react';
const Counter = () => {
// Declare a state variable named 'count' with an initial value of 0
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
useState
hook? :Use
useState
when you need to introduce and manage local state within functional components. This is particularly useful for handling dynamic data, form inputs, or any scenario where the component's state needs to change over time.useState
hooks:
// Incorrect usage
const handleClick = () => {
setState(count + 1);
console.log(count); // This may not print the updated count immediately
};
jsx
// Correct usage with functional form
const handleClick = () => {
setState((prevCount) => prevCount + 1);
};
// Incorrect usage
const handleUpdate = () => {
setState({ ...state, property: 'new value' }); // This replaces the entire state object
};
jsx
// Correct usage with state merging
const handleUpdate = () => {
setState((prevState) => ({ ...prevState, property: 'new value' })); // Merge with the previous state
};
Being mindful of these additional caveats will help you write more robust and predictable code when working with the useState hook in React.
Let’s put what we know to the test:
Another use case: Looking to update state and then pass it to the callback.
// Incorrect usage of useState
const [selectedIndex, setSelectedIndex] = useState(0);
const handleOnTabPress = useCallback(
(index: number) => {
if (typeof onTabChange === 'function') {
setSelectedIndex((selectedIndex) => index);
return onTabChange(selectedIndex);
}
},
[onTabChange],
);
Issue: You are referencing an old value of state. However, the setState operators are asynchronous and execute the current block to the end and the update state. So it is passing the old state to the callback and updating the state value.
Knowing what you know now, which do you think is the correct fix?
if (typeof onTabChange === 'function') {
setSelectedIndex(index);
return onTabChange(index);
}
if (typeof onTabChange === 'function') {
setSelectedIndex(index);
return onTabChange(selectedIndex);
}
useEffect
:useEffect
hook?The
useEffect
hook is a Swiss Army knife for handling side effects in functional components. It enables you to execute code that goes beyond the typical rendering process, making it ideal for scenarios like data fetching, managing subscriptions, or updating the DOM.useState
hook:Use the
useEffect
hook when you encounter scenarios that involve side effects in your functional components. Side effects can encompass a wide range of operations, such as fetching data from an API, subscribing to real-time updates, or manipulating the DOM. This hook is particularly useful when you need to perform such operations after the initial render or in response to changes in specific dependencies.In the provided example, the
useEffect
hook is used to initialize or update a MapWidget based on the zoomLevel dependency. This ensures that the side effect, in this case, the dependency, setting the zoom level on the map, is executed whenever the zoomLevel changes. This pattern allows you to manage and synchronize side effects with the state of your component, resulting in a more dynamic and responsive user interface.
useEffect(() => {
if (mapRef.current === null) {
mapRef.current = new MapWidget(containerRef.current);
}
const map = mapRef.current;
map.setZoom(zoomLevel);
}, [zoomLevel]);
By leveraging the
useEffect
hook carefully, you can enhance the functionality of your React components, making them more adaptable to a variety of scenarios and enabling you to create a more robust user experience.useEffect
hooks:useEffect
is a powerful tool, but it introduces a level of complexity to your components. If your component doesn't have side effects like data fetching, subscriptions, or interactions with external systems, usinguseEffect
might be unnecessary overhead. Overusing effects in situations where they're not needed can lead to less readable and maintainable code. Therefore, consider whether the behaviour you're implementing truly requires the use of `useEffect
.useEffect
will only execute on the client side. If your application relies heavily on server-rendered content and you're expecting effects to run during server rendering, you may need to explore other strategies or lifecycle methods suited for server-side operations.useEffect
will only execute on the client side. If your application relies heavily on server-rendered content and you're expecting effects to run during server rendering, you may need to explore other strategies or lifecycle methods suited for server-side operations.By understanding these caveats and nuances associated with
useEffect
, you can make informed decisions about when to use it and how to avoid common pitfalls, leading to more robust and maintainable React applications.Let’s put what we know to the test:
Another use case: We want to log an event metric to log when the page is visited and make an API call when UUID provided
useEffect(() => {
logEvent({
type: 'page visited',
});
const response = await fetchAPI(uuid);
setCountState(() => response?.count);
}
}, [uuid]);
Issue: This effect is logging an event every time the UUID changes but we only want it to log on the first visit.
Knowing what you know now, which do you think is the correct fix?
useEffect(() => {
logEvent({
type: 'page visited',
});
}, [uuid]);
useEffect(() => {
const response = await fetchAPI(uuid);
setCountState(() => response?.count);
}
}, [uuid]);
useEffect(() => {
logEvent({
type: 'page visited',
});
}, []);
useEffect(() => {
const response = await fetchAPI(uuid);
setCountState(() => response?.count);
}
}, [uuid]);
Understanding when to use useState and useEffect is crucial for writing robust and maintainable React applications. The provided examples and caveats shed light on common pitfalls and best practices associated with these hooks. By following these guidelines, developers can create more dynamic and responsive user interfaces while avoiding potential issues.