I’m working on a React v17 application where I’m using React’s Context API to manage global state. I have a context provider that fetches data from an API using JavaScript’s native fetch
function, which returns a Promise.
Here’s a simplified version of my code:
import React, { createContext, useState, useEffect } from 'react';
export const MyContext = createContext();
export const MyProvider = props => {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://myapi.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []);
return (
<MyContext.Provider value={{ data }}>
{props.children}
</MyContext.Provider>
);
};
I’m using this context in a child component like below:
import React, { useContext } from 'react';
import { MyContext } from './MyProvider';
const MyComponent = () => {
const { data } = useContext(MyContext);
return (
<div>
{data ? data.map(item => <div key={item.id}>{item.name}</div>) : 'Loading...'}
</div>
);
};
The issue I’m facing is that sometimes, the child component renders before the data is fetched, causing it to throw an error because it’s trying to map over null. However, other times, it works perfectly fine.
I thought that useEffect with an empty dependency array would ensure that the fetch operation completes before anything else renders, but that doesn’t seem to be the case.
Can anyone explain why this might be happening and how to ensure that the fetch operation always completes before the child component tries to access the data?
Thank you in advance!
2
Answers
That’s absolutely not the case. (You’d need
<Suspense>
or similar for that.) Effects do not and can not block rendering.Don’t render the subcomponent before you have the data.
If you were using TypeScript and had a properly typed context (where
data
can’t benull
), TypeScript would guard you against this mistake.You’re dealing with
fetch
function’s asynchronous nature. It returns a Promise that completes when data is fetched from the API. JavaScript doesn’t pause for this Promise to complete, so your component might render before data fetch, causing an error when trying to map overnull
.A solution: Add a loading state to your context provider.
In your child component, check if data is still loading before mapping over it:
This way, your component shows "Loading…" initially and doesn’t map over data until it’s fetched from the API.