I'm using the useState hook to populate items in an array.
const [meals, setMeals] = useState([]);
useEffect(async () =>{
const mealsData = await fetchMeals(localStorage.getItem('user_id'));
console.log(mealsData);// Array(3)
setMeals([...meals, mealsData]);
console.log(meals);//[]
}, []);
The array doesn't get copies in my setMeals method, what am I doing wrong here?
This is because
setMeals([...meals, mealsData]);
is an asynchronous call and you are logging the meals before being sure the state actually updated
console.log(meals);
You should call and check your console.log() function in another useEffect() call which runs on every state change and assures that the state actually changed
useEffect(() => {
console.log(meals);
}, [meals]);
[meals] passed as the second argument will let this useEffect() run every time meals (actually) update.
the update is asynchronous, the new value is not available immediately as you're expecting. You can find an in-depth explanation here: useState set method not reflecting change immediately
UseEffect cannot be async you should wrap it in an async function
function useMeals() {
const [meals, setMeals] = useState([]);
useEffect(() => {
async function setFetchedMeals() {
const mealsData = await fetchMeals(localStorage.getItem('user_id'));
setMeals([...meals, mealsData]);
}
setFetchedMeals();
}, []); // letting the dependency array empty will run the effect only once
console.log(meals)
return meals;
}
You are actually not doing anything wrong, state updates are not synchronous, so logging meals immediately after updating it will not show the updated value
Use an effect for the meals array to detect when it has actually changed...
useEffect(() => {
console.log(meals);
}, [meals]);
Related
I build a simple todo app with a react with an array of todos:
const todos = [description: "walk dog", done: false]
I use the following two states:
const [alltodos, handleTodos] = useState(todos);
const [opencount, countOpen] = useState(alltodos.length);
This is the function which counts the open todos:
const countTodos = () => {
const donetodos = alltodos.filter((item) => {
return !item.done;
});
countOpen(donetodos.length);
};
When I try to add a new todo, I also want to update the opencount state with the countTodos function.
const submitTodo = (event) => {
event.preventDefault();
const data = {
description: todo,
done: false,
};
handleTodos([...alltodos, data]);
console.log(alltodos);
countTodos();
};
This does not work as expected, the when I run console.log(alltodos) it will show an empty array. The function itself works, but it seems to have a "delay", I guess based on the async nature of the useState hook.
I tried to pass the countTodos function as callback like this, since I have seen something similar in class based components.
handleTodos([...alltodos, data], () => {
countTodos();
});
I get the following error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
How can I solve this problem? What is the best way for me to update the state based on another state?
I think you should useEffect, (clearly stated on the log message ). this is an example :
useEffect(()=>{
const donetodos = alltodos.filter((item) => {
return !item.done;
});
countOpen(donetodos.length);
//countTodos();
},[alltodos]];
You can refer to the documentation : https://reactjs.org/docs/hooks-effect.html
Here is an example : https://codesandbox.io/s/react-hooks-useeffect-forked-4sly8
I have a react component useEffect hook that looks like the following. I am working with firestore. I am trying to remove a value from an array in firestore when the component unmounts. However, the value is not getting removed. I tried running the firestore query in the cleanup function independently to see if that query was the problem, but it's working fine independently. It's just not getting executed when it's inside the cleanup function. I THINK the problem is that my cleanup function at the end of the useEffect hook is not getting called when the component unmounts(for example when I close the window). does anyone know what I may be doing wrong? Thank you for your help in advance
useEffect(() => {
.......
return () => {
fire.firestore().collection("ActiveUsers").doc(utilvar.teacherID).get().then((snapshot) => {
var docref = snapshot.ref;
return docref.update({
active_users : fieldValue.arrayRemove({id: currentUser.uid, name: displayName})
})
})
};
}, []);
From my observation. utilvar.teacherID might not be ready as at the time your component got mounted.
So you may want to add it to the dependable array.
Your useEffect should therefore look something like this :
useEffect(() => {
.......
return () => {
fire.firestore().collection("ActiveUsers").doc(utilvar.teacherID).get().then((snapshot) => {
if(snapshot.exist){
return snapshot.ref.update({
active_users : fieldValue.arrayRemove({id: currentUser.uid, name: displayName})
}).catch(err=>err);
})
};
}, [utilvar.teacherID]);
I added the catch as it a promise being returned. It must be handled appropriately irrespective of where it's being used.
I want to re-run the code below to reload the page whenever the value inside the invitation[1][1] changes, I update these array values using the AsyncStorage in another page, when I tried the following I got the error below.
Is there any other way to do so?
const [invitation, setInvitation] = React.useState(Array.from({length: 2},()=> Array.from({length: 2})));
React.useEffect(()=>
{
getUserId();
},invitation[1][1]);
async function getUserId() {
var keys = ["invitationFromURL","Alert"]
try {
await AsyncStorage.multiGet(keys , (err, item) =>
{
setInvitation(item);
});
} catch (error) {
// Error retrieving data
console.log(error.message);
}
}
console.log(invitation);
console.log(invitation[1][1]);
The error I get
Warning: %s received a final argument during this render, but not during the previous render. Even though the final argument is optional, its type cannot change between renders.%s, useEffect
I'm guessing you have something like
const [invitation, setInvitation] = useState([[], []]);
up at the top of your component. That means on the first useEffect run invitation[1][1] will be null. On second run it will have a value. This is not kosher in React Native. When you call useEffect the final argument must have the same type every time the component renders.
Should be a simple fix -- instead of
useEffect(() => {
getUserId();
}, invitation[1][1]);
do
useEffect(() => {
getUserId();
}, invitation);
So I have this little snippet:
const useTest = (callbackFunc) => {
const user = useSelector(selector.getUser); // a value from store
useEffect(() => {
if (user.id === 1) {
callbackFunc()
}
}, [callbackFunc, user.id])
}
On the code above if callbackFunc is not memoized (wrapped in useCallback) before passing, the useEffect will trigger an infinite loop.
Wrapping the function on a useCallback inside the hook will still trigger an infinite loop, so that's a no:
const cb = () => useCallback(() => callbackFunc, [callbackFunc]);
The above will trigger infite loop because callbackFunc.
YES I am well aware that I can just wrap the function inside useCallback before passing to this hook, my only problem with that is: there will be a high probability that other/future devs will just pass a non-memoized function when calling this hook and this is what worries me. `
I can't remove the callbackFunc on the useEffect/useCallback second parameter array because of exhaustive-deps rule - and removing it (or on this line) is also prohibited by the higher devs.
Any idea how can I possibly achieve my goal? If none thatn I'll just pray that other devs will read the hook first before using it.
You can do something like this, but you won't be able to modify the callback anymore
const funcRef = React.useRef(null)
useEffect(() => {
funcRef = callbackFunc
}, [])
useEffect(() => {
if (funcRef.current){
if (user.id === 1) {
funcRef.current()
}
}
}, [funcRef, user.id])
The thing is, that I use setState to update state.n and state.data. I want to call setState onClick, using handler. After calling setState, I perform a callback console.log to watch the changed state properties, but only state.data is changed, not state.n.
handler(e) {
this.setState((state) => {
let arr = state.data;
arr.push(fakeTableData[state.n+1]);
this.setState({n: state.n+1, data: arr});
}, console.log.bind(null, this.state.n));
}
After one handler used, I expect to see state.n incremented, but I had only state.data changed, not state.n. And I recieve a warning, like this: "Warning: An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using componentDidUpdate or a callback." I've read a tutorial on the main react site, but there not enough info.
let's try let arr = [...state.data]
You should not use setState inside function that sets state. That is why you got an error. Instead you should return state. Try this:
handler = (e) => {
this.setState((state) => {
let arr = state.data;
arr.push(fakeTableData[state.n+1]);
return {n: state.n+1, data: arr};
}, () => { console.log(this.state.n) });
}