const [updatedList, setUpdatedList] = useState([[1,2],[1,2]]);
try {
const resData = await axios.post("/getlist");
if (resData.status === 200) {
setUpdatedList(resData.data.list);
}
} catch (e) {
console.log("ndsjkncjksnc");
}
console.log(updatedList);
here resData.data.list is in the form [[122,4,3,2],[5,4,3,32],[54,5,6,7,77],[2,3,5,7,5,4]]
in console.log(updatedList), I am still getting [[1,2].[1.2]]
The array is not updating to [[122,4,3,2],[5,4,3,32],[54,5,6,7,77],[2,3,5,7,5,4]]
Any help would be highly appreciated.
I tried using setUpdatedList(updatedList=>[...updatedList,resData.data.list]), It isnt working too.
****the try block is executing i.e resData.status=200
Try to create an useEffect with your updatedList as a dependency. This will print your updatedList, as soon as it has changed.
If you put your console.log after the try catch, your component will just render, start the axios request and skip directly to the console.log (which will log the initial state [1,2],[1,2]).
import { useEffect } from "react";
useEffect(() => {
console.log(updatedList);
}, [updatedList]);
Related
I'm new to React and am working on a simple receipt scanning web app based on AWS (Amplify, AppSync, GraphQL, DynamoDB, S3). I'm using the useEffect hook to fetch data for the user currently logged in, via a GraphQL call, and am noticing duplicate runs of it. At first, there were three calls, after which I read about and disabled Strict Mode. But now, even with Strict Mode disabled, I am seeing two calls.
Debugging reveals that useEffect is called only once if I comment out setWeekly(getTotalFromItems(response)), but even as little as setWeekly() ends up creating duplicate calls.
I've perused this post as well as numerous others, but they all point to Strict Mode being the primary culprit, which is not the case here.
Network log attached below for reference.
Could someone help me understand what might be causing this double-call, and how to fix it?
import WebFont from 'webfontloader';
import React, {
useEffect,
useState
} from 'react'
import {
withAuthenticator,
Text,
View
} from '#aws-amplify/ui-react';
import {
MainLayout
} from './ui-components';
import awsExports from "./aws-exports";
Amplify.configure(awsExports);
function App({signOut, user}) {
const [weekly, setWeekly] = useState([]);
useEffect(() => {
WebFont.load({
google: {
families: ['DM Sans', 'Inter']
}
});
async function fetchWeekly(queryTemplate, queryVars) {
try {
const weeklyData = await API.graphql(graphqlOperation(queryTemplate, queryVars))
console.log(weeklyData)
const response = weeklyData.data.getUser.receiptsByPurchaseDateU.items
const total = getTotalFromItems(response) // sums 'total' in [{'date': '...', 'total': 9}, ...]
setWeekly(total)
} catch (err) {
console.log('Error fetching data.');
console.log(err)
}
}
const queryVars = {
username: user.attributes.email,
}
let d = new Date();
d.setDate(d.getDate() - 7);
d = d.toLocaleDateString('en-CA');
let tmpl = generateSummaryTemplate(d) // returns a template string based on d
fetchWeekly(tmpl, queryVars);
console.log('Complete.')
});
return ( <View >
<MainLayout/>
</View >
)
}
export default withAuthenticator(App);
The issue here is that the useEffect hook is missing a dependency array. The useEffect callback enqueues a weekly state update which triggers a component rerender and the useEffect hook is called again. This second time it again computes a value and enqueues a weekly state update. It's this second time that the state is enqueued with the same value as the current state value and React decides to bail on further rerenders. See Bailing out of State Updates.
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
The solution is to add a dependency array with appropriate dependencies. Use an empty array if you want the effect to run once after the initial render when the component mounts. In this case it seems the passed user prop is the only external dependency I see at the moment. Add user to the dependency array. This is to indicate when the effect should run, i.e. after the initial mount/render and anytime user value changes. See Conditionally firing an effect.
Example:
useEffect(() => {
...
async function fetchWeekly(queryTemplate, queryVars) {
try {
const weeklyData = await API.graphql(graphqlOperation(queryTemplate, queryVars));
const response = weeklyData.data.getUser.receiptsByPurchaseDateU.items
const total = getTotalFromItems(response) // sums 'total' in [{'date': '...', 'total': 9}, ...]
setWeekly(total);
} catch (err) {
console.log('Error fetching data.');
console.log(err);
}
}
const queryVars = {
username: user.attributes.email,
};
let d = new Date();
d.setDate(d.getDate() - 7);
d = d.toLocaleDateString('en-CA');
let tmpl = generateSummaryTemplate(d); // returns a template string based on d
fetchWeekly(tmpl, queryVars);
console.log('Complete.');
}, [user]); // <-- user is external dependency
First of add a dependency array as the second argument to the useEffect, after the callback function. Moreover, I may advice that you have service functions outside the useEffect body, don't overload it like that, is not appropirate.
It’s because setWeekly() is called within your useEffect() and triggers another render. You may remove this useState() entirely and instead return the data you need in fetchWeekly().
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 just started learning React and so far I'm liking it. But the problem is that most of the tutorials are old and still using old states instead of hooks.
Now when I'm following a tutorial, I'm trying to convert the code I see into hooks. For now I'm really stuck and could use some help for this code. I'm trying to convert the code bellow for a functionnal component. I tried using useEffect but it didn't work.
handleChangeOrder(oldIndex, newIndex){
if (oldIndex == newIndex) {return;}
const [todos] = this.state;
let newSequence =[];
todos.forEach((todos,index)=> {
newSequence.push({id: todo.id, order: index + 1 });
});
}
-------------------------------------------
static changeTodoOrderURL(){
return apiDomain + "api/todo/reorder";
}
----------------------------------------
async changeTodoOrder(order){
const url = UrlService.changeTodoOrderUrl();
try{
const response = await HttpService.post(url,{order});
return response.data;
} catch (error){console.error("Not able to change order of the todos")};
}
What I tried so far in converting it :
function handleChangeOrder(oldIndex, newIndex) {
if (oldIndex == newIndex) {
return;
}
const todos = Array.from(todolist);
let newSequence = [];
todos.forEach((todos, index) => {
newSequence.push({id: todo.id, order: index + 1 });
});
useEffect(()=>{
axios.post('http://localhost:8000/api/todo/reorder')
.then(response=>{
setList(response.data);
console.log(response)
})
.catch(err => console.log(err));
},[])
}
I'm struggling mainly with async changeTodoOrder..
You don't need to use useEffect to send API requests based on event change, useEffect used to send API requests/perform side effect based on state change or on the mounting of the component.
Also, you can't call the hooks inside a function, by using it this way you are breaking the rules of hooks.
please review the rules of Hooks here
based on your code above from the class component you can use it as it is without using useEffect
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);