skip to Main Content

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


  1. 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.

    That’s absolutely not the case. (You’d need <Suspense> or similar for that.) Effects do not and can not block rendering.

    how to ensure that the fetch operation always completes before the child component tries to access the data?

    Don’t render the subcomponent before you have the data.

    if(data === null) return <>Loading...</>;
    return (
     <MyContext.Provider value={{ data }}>
       {props.children}
     </MyContext.Provider>
    );
    

    If you were using TypeScript and had a properly typed context (where data can’t be null), TypeScript would guard you against this mistake.

    Login or Signup to reply.
  2. 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 over null.

    A solution: Add a loading state to your context provider.

    import React, { createContext, useState, useEffect } from 'react';
    
    export const MyContext = createContext();
    
    export const MyProvider = props => {
      const [data, setData] = useState([]);
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        fetch('https://myapi.com/data')
          .then(response => response.json())
          .then(data => {
            setData(data);
            setLoading(false);
          });
      }, []);
    
      return (
        <MyContext.Provider value={{ data, loading }}>
          {props.children}
        </MyContext.Provider>
      );
    };
    

    In your child component, check if data is still loading before mapping over it:

    import React, { useContext } from 'react';
    import { MyContext } from './MyProvider';
    
    const MyComponent = () => {
      const { data, loading } = useContext(MyContext);
    
      if (loading) {
        return <div>Loading...</div>;
      }
    
      return (
        <div>
          {data.map(item => <div key={item.id}>{item.name}</div>)}
        </div>
      );
    };
    

    This way, your component shows "Loading…" initially and doesn’t map over data until it’s fetched from the API.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search