Reputation: 670
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
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
App.js
and first render. At this time your data
is undefined.App.js
's data
mutated, then App.js
re-render.MyComponent.jsx
component re-render.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
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
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
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