State doesn't update immediately in React - javascript

I know I have to use useEffect but I couldn't figure out a way to make this code to work. I want the state courses to be updated immediately every single time I'm adding a new course. The data variable in the getCourses function is updated, but the update of the state doesn't happen immediately.
function CourseList() {
const [courses, setCourses] = useState([]);
const [idProf, setIdProf] = useState();
this function gets the list of courses from the database and store it in courses
const getCourses = async () =>{
const response = await fetch(`${SERVER}/api/courses`);
const data = await response.json();
setCourses(data);
// -> here setCourses doesnt update the state, but data has the updated array of courses
}
this function adds a course and then adds the professor stored in idProf to this course
const addCourse = async(course) => {
//...fetch with post method...//
getCourses(); // here i call the method but when I'm trying to add the professor to the course on the next line, the state isn't updated yet
addStudentCourse(idProf, course.cod);
}
I tried to use useEffect but it s updating the state only one time
useEffect(()=>{
getCourses();
},[]);
the courses are not updated when this function is running
const addStudentCourse = async(idStudent,codCourse)=>{
let id = -1;
//searching for the id in courses
for(let c of courses){
if(c.cod == codCourse){
id = c.id;
}
}
console.log(id)// id is still -1, it cant find the course because the state wasnt updated yet
if(id != -1){
//..adding to the database..//
}
}
return (<div></div>)
}

Your getCourses funciton is async, but you are not awaiting it. Try await getCourses().

Related

React function running before state change

I have a function that is run when a user clicks a button, when this function is run it gathers data and updates state. I then have another function which runs that uses some of the data that is added to state, the issue is the state is not updating in time so the function is using old data.
First function
async function callWeather() {
const key = "";
// Get location by user
let location = formData.location;
// Url for current weather
const currentWeatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
// Get the current weather
const currentWeatherResponse = await fetch(currentWeatherUrl);
if (!currentWeatherResponse.ok) {
// Return this message if an error
const message = `An error has occured: ${currentWeatherResponse.status}`;
throw new Error(message);
}
const weatherDataResponse = await currentWeatherResponse.json();
// Update state with data
setWeatherData(weatherDataResponse);
}
Second function
async function callForcast() {
const key = "";
// Get lat & lon from the previous data fetch
const lon = weatherData.coord.lon
const lat = weatherData.coord.lat
// Get forcast data
const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const forcastWeatherResponse = await fetch(forcastWeatherUrl);
if (!forcastWeatherResponse.ok) {
const message = `An error has occured: ${forcastWeatherResponse.status}`;
throw new Error(message);
}
const forcastDataResponse = await forcastWeatherResponse.json();
// Update state with the forcast data
setForcastData(forcastDataResponse);
}
This then runs with the onClick calling both functions
function callWeatherAndForcast() {
callForcast();
callWeather();
}
use 'await' before calling callForcast so the second function (callWeather) does'nt get called immediately after calling first function.
async function callWeatherAndForcast() {
await callForcast();
callWeather();
}
also as #tromgy mentioned in the comments, React state updates are not immediate, try calling callWeather function inside a hook which has a dependency on forcastData state
Are you using FunctionComponent or Classes ?
Also, keep in mind that updating the state will trigger a rerendering. This means that:
The state update is not immediate
If one of your functions use the data from another, you should take care of these dependencies.
For helping you correctly, I need to know if you use FunctionComponent or Class and get the whole Function/Class.
Edit: based on the fact that you're using FunctionComponent.
In order to archive what you want, you need to use hooks.
Hooks are the way to handle a function component lifecycle.
For your problem, you'll need useState, useCallback hooks.
export const DisplayWeather = () => {
const [forecast, setForecast] = useState();
const [weather, setWeather] = useState();
const [error, setError] = useState();
const onSubmit = useCallback(async () => {
getWeather();
getForecast();
}, [forecast, weather]);
const getWeather = useCallback(async () => {
const key = "";
const location = formData.location;
const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
const apiResponse = await fetch(currentWeatherURL);
if(!apiResponse.ok){
const message = `An error has occured: ${apiResponse.status}`;
setError(message);
} else {
const weatherData = apiResponse.json();
setWeather(weatherData);
}
}, [formData]);
const getForecast = useCallback(async () => {
const key = "";
const lon = weather.coord.lon;
const lat = weather.coord.lat;
const forecastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const apiResponse = await fetch(forecastWeatherUrl);
if(!apiResponse.ok) {
const message = `An error has occured: ${apiResponse.status}`;
setError(message);
} else {
const forecastData = apiResponse.json();
setForecast(forecastData);
}
}, [weather]);
if(error){
return (
<p>Error: {error}</p>
)
}
return (
<p>Forecast data</p>
<p>{forecast.data.temperature}</p>
<p>Weather data</p>
<p>{weather.data.temperature}</p>
);
}
In the code above, I set 2 state variables (weather & forecast) and create 3 functions.
The onSubmit function is called when the user click. His callback depend on two variables (weather & forecast) which are referenced in the dependency array (the [] after the callback)
The getWeather function is called before getForecast because the result of the getForecast function depends on the weather state. That's why you have weather in the getForecast callback dependency array. It tells getForecast that when the value of weather change, it needs to re-render.
Note that i've added formData into the dependency array of getWeather otherwise, when the user click, the getWeather function won't get any value from formData.
Note: it is not a working example, just a simple explanation. You can find more infos here:
Hooks Reference
useCallback Reference
State does not update immediately! Meaning that the function I want to get the new state will get the previous state. To fix this I added callForcast function into a useEffect hook which has a dependency on callWeather because callForcast needs callWeather to update state first. This means when this function is run state will be updated in time.
useEffect (() => {
async function callForcast() {
const key = "";
// Get lat & lon from the previous data fetch
const lon = weatherData.coord.lon
const lat = weatherData.coord.lat
// Get forcast data
const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const forcastWeatherResponse = await fetch(forcastWeatherUrl);
if (!forcastWeatherResponse.ok) {
const message = `An error has occured: ${forcastWeatherResponse.status}`;
throw new Error(message);
}
const forcastDataResponse = await forcastWeatherResponse.json();
// Update state with the forcast data
setForcastData(forcastDataResponse);
}
// Call the callForcast function to run
callForcast();
},
// This effect hook is dependent on callWeather
[callWeather])
Now my onClick will only need to call callWeather()
function.
Thanks to:
#Mohammad Arasteh
#Thomas Geenen
#tromgy
I think you should try to call callWeather(); under callForcast() after setForcastData() state set, and if update state value not affected in call weather you can try to add wait in setForcastData().
Or, try to add wait before callForcast() in callWeatherAndForcast() onClick

Removing a row from a React state array

I'm creating a dynamic report that you can add/ delete products from both list
1st list being the products order
2nd being the rest of the products in the db
I only want to call my api when hitting the submit button so until that point everything should be updated in my state.
The Add button works perfectly fine with the code bellow:
function onAddClicked(productId){
let newProduct = filteredProducts.find(x => x.productId === productId);
let updatedProductList = purchaseOrderDetails;
newProduct.adjustByAmount = newProduct.total;
updatedProductList.push(newProduct);
setPurchaseOrderDetails(updatedProductList);
filterDuplicates(filteredProducts, purchaseOrderDetails);
}
where setPurchaseOrderDetails is the products on order and filterDuplicates sets the state for the rest of the products all working fine here.
However, on my Delete function the state of the rest of the products seem to update but the ones on the order don't seem to go down in number as the state stays the same.
function onDeleteClicked(productId){
let excludedProducts = filteredProducts;
let deletedIndex = purchaseOrderDetails.findIndex(x => x.productId === productId);
let productToDelete = purchaseOrderDetails[deletedIndex];
productToDelete.total = 0;
productToDelete.adjustByAmount = 0;
let detailsToUpdate = purchaseOrderDetails.filter(x => x.id !== productToDelete.id)
console.log('updateDetails', detailsToUpdate)
excludedProducts.push(productToDelete);
console.log('updateFilter', excludedProducts)
setPurchaseOrderDetails(detailsToUpdate);
setFilteredProducts([...filteredProducts]);
console.log('stateDetail', purchaseOrderDetails)
console.log('stateFilter', filteredProducts)
}
I've also console logged before my setPurchaseOrderDetails and i can see the filter is working fine but when it gets to the point where in needs to update the state it does not.
the filteredProducts is not changing when you use here:
->setFilteredProducts([...filteredProducts]);
you push the productToDelete to excludedProducts not in the filteredProducts
First all setState is always async, so setPurchaseOrderDetails and setFilteredProducts are async too. Thus whatever the updates you made to state will not reflect immediately in the next line.
You can print them in some async call backs like setTimeout after you update the state or anywhere in the component re-render flow.
You might have confused by looking at filteredProducts value as its value reflected immediately after state update. It's not happening because of state update it's indeed happening because of both filteredProducts and excludedProducts are pointing to same reference and you've new element to excludedProducts.
So to avoid these kind of confusions I would suggest you to clone state before modifying it.
You can better write your delete function like below
function onDeleteClicked(productId) {
const productToDelete = purchaseOrderDetails.find(x => x.id === productId);
const detailsToUpdate = purchaseOrderDetails.filter(x => x.id !== productId);
setPurchaseOrderDetails(detailsToUpdate);
setFilteredProducts([
...filteredProducts,
{ ...productToDelete,
total: 0,
adjustByAmount: 0
}
]);
}

Properly append to an array when handling presses

Within my function, through interaction from the user, I aim slowly build up an array of responses which I then pass off to an API. However, different approaches to append to the array, simply return a single position array (overwrite).
My current code as follows:
const contribution: Array = [];
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
contribution = [...contribution, col];
}
My understanding is that contribution = [...contribution, col] is the correct way to add to the array.
What is the best practice approach for doing this inside a function called each time the user interacts?
Although it is not clear from the question, I suspect, this code is inside a component. If so, then a new contribution array is created on every render. You need to use useState to store this array so that a new array is not created on every render.
const [contribution, setContribution] = React.useState([]);
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
setContribution([...contribution, col]);
}

Firebase pagination problem, how to set the latest document reference correctly?

I am trying to perform infinite scroll in react component, but after all the data loads latestMealDoc becomes undefined.
Also same thing happens when I go to different route and come back to the component, the latest document is incorrect and I start getting the same items all over again.
Am i setting the state wrong?
const [latestMealDoc, setLatestMealDoc] = useContext(latestMealDocContext);
const getNextMeals = async () => {
const ref = db
.collection("meals")
.orderBy("timestamp")
.limit(6)
.startAfter(latestMealDoc || 0);
const data = await ref.get();
data.docs.forEach((doc) => {
const meal = doc.data();
setMealSearchResults((prev: any) => [...prev, meal]);
});
setLatestMealDoc(data.docs[data.docs.length - 1]);
};
useEffect(() => {
getNextMeals();
}, []);
Can you log the output of latestMealDoc?
What are you expecting to see for latestMealDoc?
It sounds like the undefined variable has not been assigned a value yet.
A variable that has not been assigned a value is of type undefined. A method or statement also returns undefined if the variable that is being evaluated does not have an assigned value. A function returns undefined if a value was not returned.

Svelte: is there a way to cache the api result in a way that it won't trigger the api call everytime the component renders?

It could be that I'm typing the wrong things in Google and can't get a good answer.
Is there a "svelte recommended" way to store the value of a GET result, so that, on every refresh or link switch, the result in the store is used in the component until a timeout (where the api is called again)?
My purpose is to fetch blogposts from an external API and show them in a list but not on every refresh, or link switch.
My code:
<script>
let posts = [];
onMount(async () => {
const res = await fetch(apiBaseUrl + "/blogposts");
posts = await res.json();
});
</script>
{#each posts as post}
<h5>{post.title}</h5>
{/each}
In pseudocode what i want:
if (store.blogposts.timeout === true){
onMount(...);
// renew component
}
you can use stores to achieve this. Initial page load fetch posts data from api and save in stores. Then use the posts data in further page mounts. Set timeout to true whenever you want to refresh data.
./stores.js
import {writable} from 'svelte/store';
export const posts = writable([]);
export const timeout = writable(false);
./posts.svelte
<script>
import {posts, timeout} from "./stores.js"
onMount(async () => {
if($posts.length<1 || $timeout == true){
const res = await fetch(apiBaseUrl + "/blogposts");
$posts = await res.json();
}
});
</script>
{#each $posts as post}
<h5>{post.title}</h5>
{/each}
When you refresh the page the posts in stores will clear. To avoid that use localstorage to cache data. pls check the below code.
./posts.svelte
<script>
let posts = [];
onMount(async () => {
posts = await getdata();
}
const getdata = async ()=>{
// set cache lifetime in seconds
var cachelife = 5000;
//get cached data from local storage
var cacheddata = localStorage.getItem('posts');
if(cacheddata){
cacheddata = JSON.parse(cacheddata);
var expired = parseInt(Date.now() / 1000) - cacheddata.cachetime > cachelife;
}
//If cached data available and not expired return them.
if (cacheddata && !expired){
return cacheddata.posts;
}else{
//otherwise fetch data from api then save the data in localstorage
const res = await fetch(apiBaseUrl + "/blogposts");
var posts = await res.json();
var json = {data: posts, cachetime: parseInt(Date.now() / 1000)}
localStorage.setItem('posts', JSON.stringify(json));
return posts;
}
}
{#each posts as post}
<h5>{post.title}</h5>
{/each}
svelte-query can help:
Svelte Query is often described as the missing data-fetching library for Svelte, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your Svelte applications a breeze.
note: svelte-query is abandoned and will be replaced with #tanstack/svelte-query

Categories

Resources