This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 11 months ago.
I have a simple page editor, When a user clicks edit page it opens an editor. I am passing the ID of the page using redux which will be used to get data from API.
Here is my Editor.
const [pageData, setPageData] = useState("");
const getPage = async (id) => {
try {
const response = await api.get(`/landing_pages/${id}`);
console.log("page", response.data); // displays data at the end
setPageData(response.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getPage(pageID);
console.log('Page Data', pageData) // displays nothing
let LandingPage = pageData;
const editor = grapesjs.init({
container: "#editor",
components: LandingPage.components || LandingPage.html,
})
}, [pageID, getPage])
Why is Page Data display nothing even though the data from API is returned and is displayed in the console at the end? what am I doing wrong here?
Even if you await your getPage call, the updated pageData won't be available until the next render cycle so your assignment to LandingPage will be one cycle behind.
You should instead update in one useEffect and watch for changes to pageData in another.
const [pageData, setPageData] = useState("");
useEffect(() => {
const getPage = async (id) => {
try {
const response = await api.get(`/landing_pages/${id}`);
console.log("page", response.data); // displays data at the end
setPageData(response.data);
} catch (error) {
console.log(error);
}
};
getPage(pageID);
}, [pageID]);
useEffect(() => {
console.log('Page Data', pageData); // displays updated pageData
let LandingPage = pageData;
const editor = grapesjs.init({
container: "#editor",
components: LandingPage.components || LandingPage.html,
});
}, [pageData]);
Related
Edit - added minimally reproducible example: https://snack.expo.dev/#hdorra/code
I hope everyone can access the snack. So if you add a task, you can see it show up in the log. Click on the circle, it shows as true (meaning it is clicked). Save and refresh and everything is stored (the task) but the checkbox is not. I stripped the code to make it as bare minimum as possible but it shows the problem.
It has been days of me on this error. I am relatively new to stackoverflow so my apologies if my question isn't clear or I am not asking it in the correct format. I am trying to create a to do app in react native that is using async storage. I created a toggle button that saves the toggle to a state. This button is located in a component:
const [checkBoxState, setCheckBoxState] = React.useState(false);
const toggleComplete = () => {
setCheckBoxState(!checkBoxState)
handleEdit();
console.log(checkBoxState)
}
When the user checks on it - seems to be showing up correctly as marked true and false in the console.
Then, this is passed to an edit handler to update the array, again console shows it is the correct state:
const handleEdit = () => {
props.editHandler(props.todoKey, text, checkBoxState);
console.log(text2, checkBoxState)
};
Then it shows that it saved correctly:
const [todos, setTodos] = React.useState([]);
const handleEdit = (todoKey, text, newStatus) => {
const newTodos = [...todos];
const index = newTodos.findIndex(todos => todos.key === todoKey);
newTodos[index] = Object.assign(newTodos[index], {title: text, status: newStatus});
setTodos(newTodos);
console.log(todos, newStatus)
};
The async function to save to the device and load are as follows:
To save:
const saveTodoToUserDevice = async (todos) => {
try {
const stringifyTodos = JSON.stringify(todos);
await AsyncStorage.setItem('todos', stringifyTodos);
} catch (error) {
console.log(error);
}
};
To load from the device:
const getTodosFromUserDevice = async () => {
try {
const todos = await AsyncStorage.getItem('todos');
if (todos != null) {
setTodos(JSON.parse(todos));
console.log("loaded successfully");
}
} catch (error) {
console.log(error);
}
};
So here is the issue - I get the console log that says it is saved correctly and loaded. BUT, when I refresh, the checkbox state is not saved at all, just the title text (so it is saving but the checkbox would always be false (the initial state set). If I clicked on true, it would show as true and then when I refresh, it goes back to false.
I have spent days and days on this and can't figure it out. Any direction would be helpful Thank you!
I have gone through your code and found some errors you are making in different places. In Task.js you can do without that checkBoxState. For that, pass the status to Task as props while rendering it in FlatList, like so:
<Task
key={item.key}
todoKey={item.key}
title={item.title}
status={item.status}
editHandler={handleEdit}
pressHandler={handleDelete}
/>
Then as below, change the button to toggle the status, so you use what's coming from the props and create a function called toggleStatus and pass it to onPress:
<TouchableOpacity onPress={toggleStatus}>
<View
style={[
styles.circle,
!props.status ? styles.completeCircle : styles.incompleteCircle,
]}
></View>
</TouchableOpacity>
The code for toggleStatus:
const toggleStatus = () => {
props.editHandler(props.todoKey, props.title, !props.status);
};
And handleEdit would be simplified to:
const handleEdit = () => {
props.editHandler(props.todoKey, text2, props.status);
setEdit(false);
console.log(props.status);
};
Lastly, in TasksMain.js so you don't replace what's in the storage with that initial array given to useState, make sure saveTodoToUserDevice runs after getTodosFromUserDevice. For that, add the below state in TasksMain.js and slightly change the two functions as follow:
const [loading, setLoading] = React.useState(true);
const saveTodoToUserDevice = async (todos) => {
if (loading) return;
try {
const stringifyTodos = JSON.stringify(todos);
await AsyncStorage.setItem("todos", stringifyTodos);
} catch (error) {
console.log(error);
}
};
const getTodosFromUserDevice = async () => {
try {
const todos = await AsyncStorage.getItem("todos");
if (todos != null) {
setTodos(JSON.parse(todos));
console.log("loaded successfully");
}
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
I'm using react, node express, postgres
I have a react component that is an html table that gets populated from a postgres table.
Here is parent component Materials:
const Materials = () => {
const [thickness1, setThickness] = useState(0);
const [width1, setWidth] = useState(0);
const [length1, setLength] = useState(0);
const [partTotalDemand, setTotalDemand] = useState(0);
const [partPlanned, setPlanned] = useState(0);
...
Here is a method in the component that retrieves data
// Material requirements calculation
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
const tempThickness = jsonData.parts_material_thickness
const tempWidth = jsonData.parts_material_width
const tempLength = jsonData.parts_material_length
const tempTotalDemand = jsonData.workorder_total
const tempPlanned = jsonData.parts_produced
stateSetter(tempThickness, tempWidth, tempLength)
} catch (err) {
console.log(err.message);
}
}
I then want to update the states of the global constants:
const stateSetter = (thickness, width, length) => {
try {
setThickness(thickness);
setWidth(width);
setLength(length);
console.log(thickness1);
console.log(width1);
console.log(length1);
} catch (err) {
console.log(err.message)
}
}
useEffect(() => {
stateSetter();
}, [thickness1]);
Essentially the getReq() method is supposed to retrieve the information, and then I need to update the states with those values. As I understand I then need to re-render the component so the new states are usable. I attempted to do this via useEffect() but I'm not successful. The idea was to stop getReq() from firing up on the first render, but if the state changes for thickness1/width1/length1 then it should fire up and re-render, help much appreciated!
You're over-complicating this. All you need to do is set the state values:
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
// set state values
setThickness(jsonData.parts_material_thickness);
setWidth(jsonData.parts_material_width);
setLength(jsonData.parts_material_length);
setTotalDemand(jsonData.workorder_total);
setPlanned(jsonData.parts_produced);
} catch (err) {
console.log(err.message);
}
}
You don't need to manually do anything to re-render the component. It will re-render whenever state is updated. So the "setter" functions being invoked here will trigger that re-render. (All of the state updates will be batched. So the above won't trigger 5 re-renders, just one with the 5 updated state values.)
Where you would use useEffect is when you want to have some logic which responds to a change in a particular state. For example, if you want to show a message every time thickness changes to a negative value, you'd do something like:
useEffect(() => {
if (thickness < 1) {
alert('negative thickness!');
}
}, [thickness]);
But that's not what you're doing here. All you're doing here is setting state values.
When I render this React component:
import React, { useState, useEffect, useCallback } from "react";
// import RenderLineChart from "../recharts/rechart.component";
import axios from "axios";
import "./chart-item.styles.scss";
const ChartItem = ({ apiUrl }) => {
const [chartData, setChartData] = useState([]);
//function that pulls data from APIs
const loadChartData = useCallback(() => {
axios.get(apiUrl).then((response) => {
setChartData(response.data);
});
}, [apiUrl]);
//runs initial pull from API
useEffect(() => {
loadChartData();
}, [loadChartData]);
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
// console.log("Array Element: ");
// console.log(chartData.historical[0]);
return <div className="chart"></div>;
};
export default ChartItem;
The console output is such:
When I uncomment that third console.log statement and save the component file without refreshing localhost, the output is:
But when I actually refresh the localhost tab, the output is such:
As if it is no longer being treated as an array. Can anyone explain what's going on here?
You have 2 mistakes in the snippet you posted.
You're declaring chartData as an array, but then you're updating it with an object (inside the loadChartData function)
When page loads the console.log is executed before the loadChartData function has completed the request, so at the first render chartData = [] and chartData[0] = undefined, but if you try to get chartData.historical[0] it throws you an error because it is defined as an array.
So, how can you fix this? The answer is pretty straightforward, first of all you have to use a consistend data-type for your state, if it's an array when declared, then when you update it, pass an array, if not you'll always have this kind of problems. This should fit your needs. If you want to log chartData everytime it upates you just need to write a new useEffect with the console.log inside and chartData as "dependency"
const [chartData, setChartData] = useState({ historical: [] })
// declare you api call
const apiCall = useCallback(() => {
try {
const response = await axios.get(url);
setChartData(response.data)
} catch (e) {
console.error(e)
}
}, [setChartData])
useEffect(() => {
apiCall()
}, [apiCall])
useEffect(() => {
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
console.log("Array Element: ");
console.log(chartData.historical[0]);
}, [chartData])
// do you stuff with jsx
I suggest you to take a look at Hooks Documentation
Hope this helps ✌️
Help 'm having trouble re-rendering a page on a react function component. I want to fetch data on the initial load of a page, that's why I use an uuid that is fed to the page as a props and initiated it as an empty string when the page loaded for the first time. But setting the uuid or attachment a new value doesn't re-render the page. What did I do wrong here?
const [attachments, setAttachments] = React.useState<IAttachment[]>([]);
const [uuid, setUuid] = React.useState('');
React.useEffect(() => {
if (uuid !== props.uuid) {
const fetchAttachment = async () => {
const accessToken: string = AuthHelper.GetAccessToken() || '';
if (accessToken) {
if (activeItem.AttachmentFolderId !== undefined) {
const attachments: IAttachment[] = await GraphService.getAttachments(accessToken, activeItem.CreatorId, activeItem.AttachmentFolderId);
setAttachments([...attachments]);
}
}
}
fetchAttachment();
setUuid(props.uuid);
console.log('State updated');
}
}, []);
attachments is used as a props for a list component.
I have two components which i am working with. In the first component, i made a custom useEffect hook that retrieves data from my server. Please see the code below:
Code snippet One
import {useState, useCallback} from 'react';
import {stageQuizApi} from '../api/quiz';
import {QuestionService} from "../services/IdDbServices/question_service";
const usePostData = ({url, payload, config}) => {
const [res, setRes] = useState({data: null, error: null, isLoading: false});
const callAPI = useCallback(() => {
setRes(prevState => ({...prevState, isLoading: true}));
stageQuizApi.patch(url, payload, config).then( res => {
setRes({data: res.data, isLoading: false, error: null});
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
}).catch((error) => {
console.log(error)
if (error.response) {
const errorJson = error.response.data
setRes({data: null, isLoading: false, error: errorJson.message});
} else if (error.request) {
setRes({data: null, isLoading: false, eror: error.request});
} else {
setRes({data: null, isLoading: false, error: error.message});
}
})
}, [url, config, payload])
return [res, callAPI];
}
export default usePostData;
The above module has two purpose. It first makes an axios request to my endpoint and secondly makes a database insertion to browser IndexDb (similar to localstorage but with sql approach) ( like inserting data into the database using the response that was gotten from the first request. so typically i have a promise in the outer .then block. This part:
Code snippet Two
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
Here is the problem, I am trying to maintain state as i want the result of this module to be shared in another route and i don't want to hit the server again hence i inserted the result into indexDb browser storage. Here is the code that executes the above module:
Code snippet Three
const displaySingleQuestion = ()=>{
OnUserGetQuestion();
history.push('/player/question');
}
The above method is called from my first route /question and it is expected to redirect user to the /player/question when the displaySingleQuestion is called.
On the new route /player/question i then want to fetch the data from IndexDb and update the state of that component using the useEffect code below:
Code snippet Four
useEffect(()=>{
const getAllUserFromIndexDb = async()=>{
try{
const result = await new Promise((resolve, reject) => {
service.getStudents().then(res=>resolve(res)).catch(err=>reject(err))
});
console.log('it did not get to the point i was expecting',result)
if(result[0]){
console.log('it got to the point i was expecting')
const singleQuestion = result[0];
const questionPage = playerQuestionToDisplay;
questionPage.name = singleQuestion.name;
questionPage.anwserRatio = singleQuestion.anwserRatio;
questionPage.answer = singleQuestion.answer;
questionPage.options = singleQuestion.options;
questionPage.playerId = singleQuestion.playerId;
questionPage.question = singleQuestion.question;
questionPage.timePerQuestion = singleQuestion.timePerQuestion;
return setplayerQuestionToDisplay({playerQuestionToDisplay:questionPage})
}
}
catch(error){
console.log(error)
}
}
getAllUserFromIndexDb();
return function cleanup() {
setplayerQuestionToDisplay({playerQuestionToDisplay:{}})
}
},[history.location.pathname]);
The problem is that only one Button click (Code snippet three)(displaySingleQuestion()) triggers the whole functionality and redirect to the /player/question page but in this new route the state is not been set until a page reload as occurred, i tried debugging the problem and i found out that when the button is clicked i found out that Code snippet two is executed last hence when Code snippet Four ran it was in promise and until a page reloads occurs the state of the component is undefined
Thanks for reading, Please i would appreciate any help in resolving this issue.