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
Related
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.
I am trying updating data in dispatch in useEffect but showing warning in console
React Hook useEffect has missing dependencies: 'dispatch', 'id', and 'state.selectedHotel'. Either include them or remove the dependency array react-hooks/exhaustive-deps
code
import { GlobalContext } from "../../../context/globalContext";
const HotelDetail = () => {
const [state, dispatch] = useContext(GlobalContext);
const { id } = useParams();
useEffect(() => {
const hotelData = async () => {
try {
let response = await ServiceGetAllHotels();
let hotel = response.hotels.filter(hotel => {
return hotel.hotelUserName === id;
});
dispatch({
type: "UPDATE",
payload: { selectedHotel: hotel[0] }
});
}catch(){}
};
}, [])
};
But warning message disappear when I add this (below code)
useEffect(() => {
.....
}, [dispatch, state.selectedHotel, id])
I dont understand why this error/warning , why error disappear when I add this ? Please help Can I go with this code?
Its not an error but a warning that can save you from bugs because of useEffect hook not running when it was supposed to.
useEffect hook, by default, executes after:
the initial render
each time a component is re-rendered
Sometimes we don't want this default behavior; passing a second optional argument to useEffect hook changes the default execution behavior of useEffect hook. Second argument to useEffect hook is known as its dependency array that tells React when to execute the useEffect hook.
Run "useEffect" once, after the initial render
We can achieve this by passing an empty array as the second argument to the useEffect hook:
useEffect(() => {
// code
}, []);
This effect will only execute once, similar to componentDidMount in class components.
Run "useEffect" everytime any of its dependency changes
When the code inside the useEffect depends on the state or a prop, you sometimes want useEffect to execute every time that piece of state or prop changes.
How can we tell React to run the effect every time a particular state or prop changes? By adding that state or prop in the dependency array of the useEffect hook.
Example:
Imagine a Post component that receives post id as a prop and it fetches the comments related to that post.
You might write the following code to fetch the comments:
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, []);
Problem with the above code:
When the Post component is rendered for the first time, useEffect hook will execute, fetching the comments using the id of the post passed in as the argument.
But what if the post id changes or the post id is not available during the first render of the Post component?
If post id prop changes, Post component will re-render BUT the post comments will not be fetched because useEffect hook will only execute once, after the initial render.
How can you solve this problem?
By adding post id prop in the dependency array of the useEffect hook.
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, [props.postId]);
Now every time post id changes, useEffect will be executed, fetching the comments related to the post.
This is the kind of problem you can run into by missing the dependencies of the useEffect hook and React is warning you about it.
You should not omit any dependencies of the useEffect hook or other hooks like: useMemo or useCallback. Not omitting them will save you from such warnings from React but more importantly, it will save you from bugs.
Infinite loop of state update and re-render
One thing to keep in mind when adding dependencies in the dependency array of the useEffect is that if your are not careful, your code can get stuck in an infinite cycle of:
useEffect --> state update ---> re-render --> useEffect ....
Consider the following example:
useEffect(() => {
const newState = state.map(...);
setState(data);
}, [state, setState]);
In the above example, if we remove the state from the dependency array, we will get a warning about missing dependencies and if we add state in the array, we will get an infinite cycle of state update and re-render.
What can we do?
One way is to skip the state as a dependency of the useState hook and disable the warning using the following:
// eslint-disable-next-line react-hooks/exhaustive-deps
Above solution will work but it's not ideal.
Ideal solution is to change your code in such a way that allows you to remove the dependency that is causing the problem. In this case, we can simply use the functional form of the setState which takes a callback function as shown below:
useEffect(() => {
setState(currState => currState.map(...));
}, [setState]);
Now we don't need to add state in the dependency array - problem solved!
Summary
Don't omit the dependencies of the useEffect hook
Be mindful of the infinite cycle of state update and re-render. If you face this problem, try to change your code in such a way that you can safely remove the dependency that is causing the infinite cycle
The useEffect hook accepts two arguments. The first one is a classic callback and the second one is an array of so called "dependencies".
The hook is designed to execute the callback immediately after component has been mount (after elements have been successfully added to the real DOM and references are available) and then on every render if at least one of the values in the dependencies array has changed.
So, if you pass an empty array, your callback will be executed only once during the full lifecycle of your component.
It makes sense if you think about it from a memory point of view. Each time that the component function is executed, a new callback is created storing references to the current execution context variables. If those variables change and a new callback is not created, then the old callback would still use the old values.
This is why "missing dependencies" is marked as a warning (not an error), code could perfectly work with missing dependencies, sometimes it could be also intentional. Even if you can always add all dependencies and then perform internal checks. It is a good practice to pass all your dependencies so your callback is always up to date.
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
First of all i am new to react hooks. hence please bear if it's silly.
So, i want to dispatch actions one after another in useEffect hook (on Load).
Also my second dispatch needs data from first dispatch.
Here is what i have written which is not working.
const userData = useSelector((state) => state.user.data);
useEffect(() => {
dispatch(fetchUser("someUserId")).then(() => {
dispatch(fetchProjects("someUserId", userData.settings.startDate));
});
}, []); // on load
In the above code, userData.settings is fetched from first dispatch. When i run the code i get an error saying userData.settings is not defined.
Please let me know, what is wrong here.
Thanks in advance
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.