matmik
matmik

Reputation: 670

Error when accessing data from an API in React

I have an issue with data not being available in the app that is returned from an API when my component is being rendered. I am using the async await syntax on the API call, but missing something essential as it errors out.

I'm using Axios to make the API call, and Axios-Mock-Adapter to create a mock response.

Simplified example of the code:

App.js

  const [data, setData] = useState();

  const MainContext = createContext({
    data: {},
  });

  useEffect(() => {
    const getPosts = async () => {
      const response = await axios.get(
        "/mockedEndpoint"
      );
        setData(response.data);
    };

    getPosts();
  }, []);


  return (
     <MainContext.Provider value={data}>
       <MyComponent />
     </MainContext.Provider>
  );

MyComponent.jsx

  const data = useContext(MainContext);
  console.log("data", data); // first prints empty object, then a second later with correct data
  console.log("data", data.user); // if I have this line, it throws an error (user does exist a second later)

Am I missing something obvious? Do I need to handle the data in MyComponent.jsx differently? I imagined that having the getPosts be an async function would let me access the returned values from the API in my component.

Upvotes: 1

Views: 223

Answers (4)

HsuTingHuan
HsuTingHuan

Reputation: 625

This is because the React's One Way Data Flow.

Please take a look this page about React's One Way Data Flow for your understanding.

You are using asynchronous calling, at the mean time you are not setting the initial state to App.js's data state.

const [data, setData] = useState();

According to React docs

The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This default value can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming -components to use defaultValue.

You set data to your MainContext's provider, this will assign data to MainContext's default value { data:{} }.

  return (
     <MainContext.Provider value={data}>
       <MyComponent />
     </MainContext.Provider>
  );

So the render steps will look like

  1. Initialize App.js and first render. At this time your data is undefined.
  2. App.js's data mutated, then App.js re-render.
  3. MyComponent.jsx component re-render.

Solution

The simple solution is to add a undefined check to data in your MyComponent.jsx component, like this

const data = useContext(MainContext);
 
retrun <>
  // 📌 check data is not undefined
  { data ? <div>{data}</div> : null }
</>

Or add a default value to your data state

const [data, setData] = useState({});

Upvotes: 1

shahzaib ghumman
shahzaib ghumman

Reputation: 21

When you use useContext:

  const data = useContext(MainContext);

Do this:

  const data = useContext(MainContext);
  useEffect(() => {
    if(data.user) {
      console.log(data.user);
    }
  }, [data])

This way, React listens to changes in context, when something changes and if data contains user, console.log the user... This is actually due to the fact you are making an async request, there's no guarantee that your data contains user...

Upvotes: 1

Marcell Bolub&#225;s
Marcell Bolub&#225;s

Reputation: 134

You are making an async request. There is no guarantee the data will be there when MyComponent.jsx runs. I'm not familiar with React, but I think you could set data to the fetch and resolve the promise in MyComponent.jsx

App.js

  const [data, setData] = useState();

  const MainContext = createContext({
    data: {},
  });

  useEffect(() => {
    const getPosts = async () => {
        setData(axios.get("/mockedEndpoint"));
    };

    getPosts();
  }, []);


  return (
     <MainContext.Provider value={data}>
       <MyComponent />
     </MainContext.Provider>
  );

MyComponent.jsx

  const data = useContext(MainContext);
  data.then(r => {
      console.log("data", r.data);
  })

Upvotes: 1

Konflex
Konflex

Reputation: 485

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

console.log("data", data?.user);

data is not available when the console.log is executed so it throw an error. With a ?. optional chaining operator it will be no error.

Upvotes: 1

Related Questions