I am trying to fetch data from a NodeJS server I created in React using the useEffect hook and the fetch API. the problem is I cannot access this data outside the useEffect callbacks
This is my code:
import React, { useEffect, useState } from "react";
const App = () => {
const [LCS_Data, setLCSData] = useState({});
useEffect(() => {
fetch("http://localhost:5000/lcs")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
console.log(data.LCS.info);
setLCSData(data);
});
}, []);
// console.log(LCS_Data.LCS.info);
return <div className="main"></div>;
};
export default App;
and this the output of the two console.logs in the useEffect: output of first console.log in the broswer
So everything is working fine, the data is structured as it should be and it is not corrupted in any way
that is until I uncomment the console.log before the return statement
it throws of this error
TypeError: Cannot read properties of undefined (reading 'info')
pointing at the last console.log when clearly the data is there.
How can I access properties inside my data object outside the scope of the useEffect hook so I can normally use it in my app
In the first render, LCS_Data has the value you provided in your useState ({}). useEffect only runs after your render, so when we reach your console.log, accessing .LCS.info from {} gives an error. Only after a rerender triggered by setLCSData(data) when the fetch call returns will LCS_Data be the value returned from the API. So in this scenario, you can either:
Pass a default value like { LCS: { info: 'default info' } } to useState so that before the useEffect runs and the fetch call returns, you have a value for LCS_Data.LCS.info to be printed out.
Use optional chaining (LCS_Data.LCS?.info) to print out undefined if there is no value yet.
Note that this is how it should work: you will not have the value from the server until the useEffect fires and the fetch call returns. What you should do is to provide a default value before this happens.
Related
I am trying to get info from an api but the useState() doesn't work correctly.
I have a work order grid by double click on each row I get the work order id then I should get the information from a specific api route to "workorder/:id" and display them. but when I try to console log the information by double click on a row I get "undefined"
here is my code:
const gridOptions = {
onRowDoubleClicked: openWorkOrder,
}
function openWorkOrder(row) {
const workOrderId = row.data.id
navigate(`workorder/${workOrderId}`)
fetch(`baseURL/api/Gages/WorkFlow/GetProductDetailByOrderId?id=${workOrderId}`)
.then((result) => result.json())
.then((data) => props.setDetails(data))
console.log(props.details)
}
const [details, setDetails] = useState() is defined in the parent component.
The function returned by useState does not update the state immediately. Instead, it tells React to queue a state update, and this will be done once all event handlers have run.
Once the component is re-rendered, you will then see the new state.
When you console.log right after the fetch, the component has not yet re-rendered, and hence you see undefined.
Fetch is async, and you placed console.log() after it, so there are no props.details at this moment. You can try to convert openWorkOrder to async function and await for fetched results.
The useState in React is an asynchronous hook (Reference).
When you call useState, it doesn't update state immediately.
If you want to get updated state, you must use useEffect hook.
import { useEffect } from "react";
useEffect(() => {
console.log(details)
},[details]);
For more Detail about React useEffect hook refer to documentation
Also Refer to Is setState() method async? and Change is not reflected and await useState in React for more detail
On the screen I get : Hello { "name" : "Jack" }
In console I get:
App rendering
useFetch useEffect
App rendering
Because what I have seen in the console , I think App.js must have ran 2 times and useFetch must have ran 1 time .
I think this is behind :
App.js calls useFetch and writes on console that App rendering . App.js does NOT wait useFetch to complete, so Hello undefined is on the screen . BUT we don't see undefined on the screen , because it is so fast and it goes away -> because of 2. step :
While App.js writes on console and displays undefined on screen , useFetch is running . When useFetch completes its running it gives back the correct object ( Jack )
App.js component runs again and writes again on console and displays the correct object ( Hello Jack )
But if this is true , then why runs App.js 2 times ?
Somebody said that my theory about this is false and useFetch gives back 2 times value . First it gives back null as object and second time the correct value , which is from fetch . So he said that useFetch does not wait fetch function to complete , so it gives back first time null and when fetch function completes then it gives back Jack .
But if he has right , then why is that ? Why does not wait useFetch to complete its functions to complete ?
Can somebody write step by step whats happening behind ?
App.js :
import { useFetch } from './useFetch';
function App() {
const { data } = useFetch({ url : "jack.json" })
console.log('App rendering')
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div> )
}
export default App;
useFetch.js :
import { useState, useEffect } from "react";
export const useFetch = (options) => {
const [data, setData] = useState(null);
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, [])
return { data }
}
My Question inspired this :
https://youtu.be/dH6i3GurZW8?t=316
Your thinking is correct.
But if this is true , then why runs App.js 2 times ?
Because App is a react component and like all other react components it will re-render whenever its data dependencies change. In your case, App will re-render whenever the value of data changes.
Your custom useFetch hook uses the useEffect hook to fetch data. Effects in React are performed after react components have rendered. That's why the console logs the first "App rendering". Once the useFetch hook calls setData, the value of data is updated which triggers the second re-render of App.
Here is a step-by-step of what's happening:
function App() {
const { data } = useFetch({ url : "jack.json" })
console.log('App rendering')
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div> )
}
Before I begin, it may help to also understand how the react component lifecycle works:
From the start, it's just like calling a regular function...
If you refer to the diagram above, we are at the stage called render, because the body of a functional component is the equivalent of the render function in class components.
First, useFetch is called
const { data } = useFetch({ url : "jack.json" });
Inside useFetch
const [data, setData] = useState(null);
At this point, data is null
Next useEffect is called
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, []);
useEffect creates an effect watcher. This is basically a function which is invoked every time one of its dependencies changes. However, it is not invoked until after the first time the component renders.
If you refer to the diagram above, the watcher will only be invoked for the first time at the point called componentDidMount.
Back inside App:
(image reposted to avoid scrolling)
console.log('App rendering')
We get a nice console.log output...
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div>
);
Here we return control back to react, and it will now attempt to take our html and mount it inside the dom.
Note that data is still null. Every other component react encounters will go through the same process described above.
So far, the steps leading up to this point can be summarized as Mounting (See the diagram above), and we have now reached the componentDidMount stage.
componentDidMount
React will now invoke our effect.
Note: If you had more than one effect, react will invoke all of them, and none will be skipped for this first round of effectual work that react does. Also they will all be invoked in the same order you declared them in.
useEffect watcher invoked
console.log("useFetch useEffect");
Another nice printout to console.log
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
fetch is invoked, but since we are not allowed to wait for promises inside effects, the fetch just runs.
Note: If your effect returns any function, react saves it to run it the next time an update/unmount occurs.
At this point, we just kinda idle until something interesting hap...oh wait fetch is done.
State Updates (setData(...))
(image reposted to avoid scrolling)
.then( json => setData(json))
setData will update the state of the component. If you refer to the diagram above once again, you'll see that when a state update occurs, the next step is to render.
Therefore, react will once again repeat the same steps as above, but it does not re-create the useEffect, or the useState again, because it has stored them from the previous render.
Sidenote: This is also how react is able to detect when you are calling a hook (useEffect, useState, etc) conditionally. If react detects a new hook after the first render is finished, it will warn you of this
App is rendered again
console.log('App rendering')
Next...
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div>
);
And we once again return the html content of our component (this time data is now whatever was sent to setData).
Updating
As you can tell, we are now in the Updating Phase. From now on, react will simply wait for props/state updates, and runs any effects that depend on them in the componentDidUpdate stage of the lifecycle
Sidenote: Your useEffect should depend on options.url so that it is re-run when the url changes
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, [options?.url]);
Unmounting
If by some bad luck, your component is unmounted either by the parent, or by some strange magic, your component will be moved into the Unmounting phase. Once again, refer to the diagram above
In this phase, none of the effects are run. Instead, any functions returned by your "effect watcher", will be invoked to do cleanup.
Note: All the cleanup functions will be run in the order they were encountered (typically top-down)
i am building a react app in which i am using firebase for authentication. the code is as follows:
function signIn(){
firebase.auth().signInWithPopup(provider).then(function(result)=>{
console.log(result.user);
setUserInfo(result.user);
console.log(userInfo);
}).catch(function(error)=>{
console.log(error.message);
});
}
const [userInfo, setUserInfo] = useState({});
now when console logging result.user, the data is perfect. but when i assign it to userInfo and console log it, it returns an empty object with something like this-
{proto}
Calls to the setter of a useState hook are asynchronous, so you can't log the state variable straight after calling setUserInfo.
If you want to get the updates state, you can use a useEffect hook. But within your current then() listener I'd recommend simply sticking to result.user.
Also see:
Is useState synchronous?
useState set method not reflecting change immediately
I have a synchronous flow
const data = store.getState().data
// ... rest of the code
If data === undefined I like to to get it and store in data const similar to this
const data = store.getState().data || getData()
// wait till the data not undefined
// ... rest of the code
getData() is just a wrapper for the dispatch({ type: GET_DATA_ASYNC })
since this dispatch is async it will be caught and executed by a saga watcher and the data object will be updated.
My problem that I can't figured out how to handle this gracefully, to be clear, I want to:
get value from the store
if there is no value - request store update (by already existing action)
when the this particular value is updated - get it back
and store it in the data const
BTW: This is all happening in an external util function
If you are using React hooks, you can use the useSelector hook from "react-redux".
This hook allows you to directly access the store and get the data whenever the store is updated.
import { useSelector } from "react-redux";
export default function Test() {
const data = useSelector(store => store.__reducerVariable_name__);
console.log(data);
// ...
return data;
}
I've been having a difficult time updating state inside of my React application lately using the useState hook.
If I define my state in a provider as -
const [session, setSession] = useState({});
const [sessionId, setSessionId] = useState(0);
And then try to set it using the setSession
setSession(response.data);
It always comes back as the default value. This all happens inside of the provider component - i.e. I'm trying to access the information within other functions in that same provider.
However, if I store that data in localStorage, for example, I have no issues accessing it whatsoever.
localStorage.setItem("session", JSON.stringify(response.data));
I've verified that the information coming from the server is an object, and that the correct data. There's no errors or promises, just the object containing the response. If I put the snippet the setSession(response.data) and localStorage.setItem("session", JSON.stringify(response.data)) next to each other, the setSession leaves the session value as {} whereas setting the local storage works perfectly. Both are using the same API response, same data
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session)
console.log(sessionId)
closeStartSessionModal();
// If I redirect my application like this, it works fine. The ID is the correct value being returned by the server
window.location = "/#/reading-sessions/" + data.id;
}
// All of this code below is wrapped in a function. None of this code is being executed at the top level
let response = await axios({
method: method,
data:data,
url: url,
headers: headers
});
await updateStateAfterSessionInitialization(response.data);
Literally all of the data is working perfectly fine. The server responds with the correct data, the correct data is stored the session in local storage. If I redirect using the ID from the object from the server, it works fine. But if I try to update the state of the component and access the state properly, it just just doesn't work, and as a result I'm having to try to find ways of working around setting the state.
Is there something that I'm misunderstanding here?
The code that I'm working with is here - https://github.com/aaronsnig501/decyphr-ui/commit/ef04d27c4da88cd909ce38f53bbc1babcc3908cb#diff-25d902c24283ab8cfbac54dfa101ad31
Thanks
The misunderstanding you have here is an assumption that state updates will reflect immediately which is incorrect
State update is async and will only refect in the next render cycle. If you try to update state and log it in the next line, you wouldn't see and updated state
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session) // This is expected to log previous value
console.log(sessionId) // This is expected to log previous value
closeStartSessionModal();
window.location = "/#/reading-sessions/" + data.id;
}
Now localStorage is synchronous and hence its update is reflected immediately
If you wish to see if the update to state was done correctly you could write a useEffect that depends on it
useEffect(() => {
console.log('State session/sessionId updated');
}, [session, sessionId])
Now depending on what you are trying to achieve you would need to modify your code in line with the above statement that state update calls are asynchronous.
Also setSession doesn't return a promise so you can't just use async await with it. You need to make use of useEffect to take an action on state update
For Example:-
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios("https://jsonplaceholder.typicode.com/posts");
console.log(result.data);
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.map(res => (
<li>{res.title}</li>
))}
</ul>
);
}
export default App;
Check your type of response.data and defined the types
array - []
objects - {}
string - ""
number - 0 in useState
setState is async, so new value will apply on the next rerender not in the next line.