No matter whether you’re a React developer with multiple years of experience or just starting out in the field, it’s guaranteed that you’ll come across error messages at some point. Whether you write code that causes these errors isn’t important — nobody writes perfect code, and we’re lucky that React helps us out by making sure we’re staying on the right track.
However, what is important is your approach to solving these error messages. Coming across them, searching them on Google, and fixing your code based on other people’s experiences is one way.
Another way — and perhaps, a better one — is to understand the details behind the error and why it’s an issue in the first place.
This article will help you understand these details by going over some of the most common React error messages and explaining what they mean, what their consequences are, and how to fix them.
We’ll be covering the following error messages:
key
propuseXXX
is called conditionally. React Hooks must be called in the exact same order in every component renderThis will help you better understand the underlying errors and prevent you from making similar mistakes in the future.
key
propimport { Card } from "./Card"; const data = [ { id: 1, text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, { id: 2, text: "Phasellus semper scelerisque leo at tempus." }, { id: 3, text: "Duis aliquet sollicitudin neque," } ]; export default function App() { return ( <div className="container"> {data.map((content) => ( <div className="card"> <Card text={content.text} /> </div> ))} </div> ); }
One of the most common things in React development is taking the items of an array and using a component to render them based on the content of the item. Thanks to JSX, we can easily embed that logic into our component using an Array.map
function and return the desired components from the callback.
However, it’s also common to receive a React warning in your browser’s console saying that every child in a list should have a unique key
prop. You’ll likely run into this warning several times before making it a habit to give each child a unique key
prop, especially if you’re less experienced with React. But how do you fix it before you’ve formed the habit?
As the warning indicates, you’ll have to add a key
prop to the most outer element of the JSX that you’re returning from the map
callback. However, there are several requirements for the key that you’re gonna use. The key should be:
export default function App() { return ( <div className="container"> {data.map((content) => ( <div key={content.id} className="card"> <Card text={content.text} /> </div> ))} </div> ); }
Although your app won’t crash if you don’t adhere to these requirements, it can lead to some unexpected and often unwanted behavior. React uses these keys to determine which children in a list have changed, and use this information to determine which parts of the previous DOM can be reused and which it should recompute when components are re-rendered. Therefore, it’s always advisable to add these keys.
Building upon the previous warning, we’re diving into the equally common ESLint warning regarding the same topic. This warning will often present after you’ve made a habit of including a key
prop with the resulting JSX from a list.
import { Card } from "./Card"; // Notice that we don't include pre-generated identifiers anymore. const data = [ { text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, { text: "Phasellus semper scelerisque leo at tempus." }, { text: "Duis aliquet sollicitudin neque," } ]; export default function App() { return ( <div className="container"> {data.map((content, index) => ( <div key={index} className="card"> <Card text={content.text} /> </div> ))} </div> ); }
Sometimes, you won’t have a unique identifier attached to your data. An easy fix is to use the index of the current item in the list. However, the problem with using the item’s index in the array as its key is that it’s not representative of that particular item across renders.
Let’s say that we have a list with several items, and that the user interacts with them by removing the second item. For the first item, nothing has changed to its underlying DOM structure; this is reflected in its key, which stays the same, 0
.
For the third item and beyond, their content hasn’t changed, so their underlying structure also shouldn’t change. However, the key
prop from all the other items will change because the keys are based on the array index. React will assume that they’ve changed and recompute their structure — unnecessarily. This negatively affects performance and can also lead to inconsistent and incorrect states.
To solve this, it’s important to remember that keys don’t necessarily have to be identifiers. As long as they’re unique and representative of the resulting DOM structure, whatever key you want to use will work.
export default function App() { return ( <div className="container"> {data.map((content) => ( <div key={content.text} className="card">{/* This is the best we can do, but it works */} <Card text={content.text} /> </div> ))} </div> ); }
useXXX
is called conditionally. React Hooks must be called in the exact same order in every component renderWe can optimize our code in different ways during development. One such thing you can do is make sure that certain code is only executed in the code branches where the code is necessary. Especially when dealing with code that is time or resource-heavy, this can make a world of difference in terms of performance.
const Toggle = () => { const [isOpen, setIsOpen] = useState(false); if (isOpen) { return <div>{/* ... */}</div>; } const openToggle = useCallback(() => setIsOpen(true), []); return <button onClick={openToggle}>{/* ... */}</button>; };
Unfortunately, applying this optimization technique to Hooks will present you with the warning to not call React Hooks conditionally, as you must call them in the same order in every component render.
This is necessary because, internally, React uses the order in which Hooks are called to keep track of their underlying states and preserve them between renders. If you mess with that order, React will, internally, not know which state matches with the Hook anymore. This causes major issues for React and can even result in bugs.
React Hooks must be always called at the top level of components — and unconditionally. In practice, this often boils down to reserving the first section of a component for React Hook initializations.
const Toggle = () => { const [isOpen, setIsOpen] = useState(false); const openToggle = useCallback(() => setIsOpen(true), []); if (isOpen) { return <div>{/* ... */}</div>; } return <button onClick={openToggle}>{/* ... */}</button>; };
An interesting aspect of React Hooks is the dependencies array. Almost every React Hook accepts a second argument in the form of an array, inside of which you’re able to define the dependencies for the Hook. When any of the dependencies change, React will detect it and re-trigger the Hook.
In their documentation, React recommends developers to always include all variables in the dependencies array if they’re used in the Hook and affect the component’s rendering when changed.
To help with this, it’s recommended to make use of the exhaustive-deps rule
inside the react-hooks ESLint plugin
. Activating it will warn you when any React Hook doesn’t have all dependencies defined.
const Component = ({ value, onChange }) => { useEffect(() => { if (value) { onChange(value); } }, [value]); // `onChange` isn't included as a dependency here. // ... }
The reason you should be exhaustive with the dependencies array matters is related to the concept of closures and scopes in JavaScript. If the main callback of the React Hook uses variables outside its own scope, then it can only remember the version of those variables when it was executed.
But when those variables change, the closure of the callback can’t automatically pick up on those changed versions. This can lead to executing your React Hook code with outdated references of its dependencies, and result in different behavior than expected.
For this reason, it’s always recommended to be exhaustive with the dependencies array. Doing so addresses all possible issues with calling React Hooks this way, as it points React towards the variables to keep track of. When React detects changes in any of the variables, it will rerun the callback, allowing it to pick up on the changed versions of the dependencies and run as expected.
When dealing with async data or logic flows in your components, you may encounter a runtime error in your browser’s console telling you that you can’t perform a state update on a component that is already unmounted. The issue is that somewhere in your components tree, a state update is triggered onto a component that is already unmounted.
const Component = () => { const [data, setData] = useState(null); useEffect(() => { fetchAsyncData().then((data) => setData(data)); }, []); // ... };
This is caused by a state update that is dependent on an async request. The async request starts somewhere in the lifecycle of a component (such as inside a useEffect
Hook) but takes a while to complete.
Meanwhile, the component has already been unmounted (due to e.g. user interactions), but the original async request still finishes — because it’s not connected to the React lifecycle — and triggers a state update to the component. The error is triggered here because the component doesn’t exist anymore.
There are several ways to address this, all of which boil down to two different concepts. First, it’s possible to keep track of whether the component is mounted, and we can perform actions based on that.
While this works, it’s not recommended. The problem with this method is that it unnecessarily keeps a reference of unmounted components around, which causes memory leaks and performance issues.
const Component = () => { const [data, setData] = useState(null); const isMounted = useRef(true); useEffect(() => { fetchAsyncData().then(data => { if(isMounted.current) { setData(data); } }); return () => { isMounted.current = false; }; }, []); // ... }
The second — and preferred — way is to cancel the async request when the component unmounts. Some async request libraries will already have a mechanism in place to cancel such a request. If so, it’s as straightforward as cancelling the requst during the cleanup callback of the useEffect
Hook.
If you’re not using such a library, you could achieve the same using AbortController
. The only downsides to these cancel methods are that they are fully reliant on a library’s implementation or browser support.
const Component = () => { const [data, setData] = useState(null); useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }).then((data) => setData(data)); return () => { controller.abort(); } }, []); // ... };
Infinite loops are the bane of every developer’s existence and React developers are not an exception to this rule. Luckily, React does a very nice job of detecting them and warning you about it before your entire device becomes unresponsive.
As the warning suggests, the problem is that your component is triggering too many re-renders. This happens when your component queues too many state updates in a very short amount of time. The most common culprits for causing infinite loops are:
If you’re running into this particular warning, make sure to check those two aspects of your component.
const Component = () => { const [count, setCount] = useState(0); setCount(count + 1); // State update in the render return ( <div className="App"> {/* onClick doesn't receive a proper callback */} <button onClick={setCount((prevCount) => prevCount + 1)}> Increment that counter </button> </div> ); }
In React, there are a lot of things that we can render to the DOM in our components. The choices are almost endless: all the HTML tags, any JSX element, any primitive JavaScript value, an array of the previous values, and even JavaScript expressions, as long as they evaluate to any of the previous values.
Despite that, unfortunately, React still doesn’t accept everything that possibly exists as a React child. To be more specific, you can’t render objects and functions to the DOM because these two data values will not evaluate to anything meaningful that React can render into the DOM. Therefore, any attempts to do so will result in React complaining about it in the form of the mentioned errors.
If you’re facing either of these errors, it’s recommended to verify that the variables that you’re rendering are the expected type. Most often, this issue is caused by rendering a child or variable in JSX, assuming it’s a primitive value — but, in reality, it turns out to be an object or a function. As a prevention method, having a type system in place can significantly help.
const Component = ({ body }) => ( <div> <h1>{/* */}</h1> {/* Have to be sure the `body` prop is a valid React child */} <div className="body">{body}</div> </div> );
One of React’s biggest benefits is being able to construct an entire application by combining a lot of smaller components. Every component can define its piece of UI in the form of JSX that it should render, which ultimately contributes to the application’s entire DOM structure.
const Component = () => ( <div><NiceComponent /></div> <div><GoodComponent /></div> );
Due to React’s compounding nature, a common thing to try is returning two JSX elements in the root of a component that is only used inside another component. However, doing so will surprisingly present React developers with a warning telling them they have to wrap adjacent JSX elements in enclosing tags.
From the perspective of the average React developer, this component will only be used inside of another component. So, in their mental model, it makes perfect sense to return two elements from a component because the resulting DOM structure would be the same, no matter whether an outer element is defined in this component or the parent component.
However, React isn’t able to make this assumption. Potentially, this component could be used in the root and break the application, as it will result in an invalid DOM structure.
React developers should always wrap multiple JSX elements returned from a component in an enclosing tag. This can be an element, a component, or React’s Fragment, if you’re sure that the component doesn’t require an outer element.
const Component = () => ( <React.Fragment> <div><NiceComponent /></div> <div><GoodComponent /></div> </React.Fragment> );
Coming across errors during development is an inevitable part of the process, no matter the amount of experience you have. However, the way you handle these error messages is also indicative of your ability as a React developer. To do so properly, it’s necessary to understand these errors and know why they’re happening.
To help you with this, this article went over eight of the most common React error messages that you’ll encounter during React development. we covered the meaning behind the error messages, the underlying error, how to address the error, and what happens if you don’t fix the errors.
With this knowledge, you should now understand these errors more thoroughly and feel empowered to write less code that contains these bugs, leading to higher quality code.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
One Reply to "8 common React error messages and how to address them"
There is a nice shorthand for using … around adjacent JSX elements: Use … instead. It does the same thing, with less typing :-).