I want to understand why this works the way it does - javascript

I've been working with React for a while now buh I always have this problem when I'm trying to update a state in either redux or react useState.
const [files, setFiles] = useState(null);
const handleFileUpload = e => {
setFiles(e.target.files[0]);
setTimeout(() => {
console.log(files);
}, 5000);
}
I ran the code without setTimeout initially but apparently the logged value is null in the first instance but the state gets updated after console.log runs so I use setTimeout as a fallback to test the bit of code as setTimeout is asynchronous and hence non blocking but still the state is only updated after setTimeout runs why please? What am I doing wrong?
Note: I only assume the state is been updated because I set the value of the input file to be file.name so as to make state the single source of truth

Related

sample state in the next line to setState in reactjs

I have a react js question where I am unsure if I can sample a state right after it has been set. Example code below
const [state, setState] = useState<boolean>(false);
setState(val => !val);
axios.post(val);
I think the setState is a call which schedules the state change and then the react engine will eventually change it. So after executing the setState on the next line if I sample 'val' it will be 'false'.
If the above is true, I have a usecase wherein in a single execution flow i need to process and change a state and send the processed state to the backend.
Now, to bend around the above issue I see 2 paths.
Sample the state in a local var. process the var and setState the var and send the var
var stateL = !state;
setState(stateL);
axios.post(stateL);
I use a new state variable trigger and do the axios call on the useEffect of this new state. (i am not using the original state in the useEffect as there are many other changes possible for this state which doesnt need an axios call)
const [triggerBackend, setTriggerBackend] = useState(false)
setState(val => !val);
setTriggerBackend(val => !val);
useEffect(() => {
axios.post(state)
}, [triggerBackend])
I am not sure if '2' is race free as well, so leaning more on '1'.
So my question is, is the way a problem like this can be solved or are there better solutions.
Thanks in advance.

UseEffect dosen't trigger setState when using sessionStorage

Hey I've go a problem with this code.
const [itemsCart, setCart] = useState([]);
useEffect(() => {
(async function (){
const buffer = await JSON.parse(window.sessionStorage.getItem("itemsCart"))
setCart(buffer);
console.log(buffer);
console.log(itemsCart);
})()
}, []);
useEffect(() => {
window.sessionStorage.setItem("itemsCart", JSON.stringify(itemsCart));
}, [itemsCart]);
The buffer gets the data, the state variable dosen't. I assume there must be a problem with synchronization however I'm not able to fix that.
The output:
here
This happens because react will wait until all script in useEffect is called and after that, setState will trigger rerender. Because there can be multiple setStates and we want to rerender it only once. That means, you are logging old value in console.log(itemsCart) before its actually there after rerender.
You can logi it with second useEffect before updating sessionStorage and you will see, that state is changed. Or you can create new useEffect for this
useEffect(()=>{
console.log(itemsCart)
},[itemsCart]);
this works:
const [itemsCart, setCart] = useState(JSON.parse(window.localStorage.getItem("itemsCart")));
useEffect(() => {
console.log(itemsCart);
window.localStorage.setItem("itemsCart", JSON.stringify(itemsCart));
}, [itemsCart]);
To run the second useEffect(), itemsCart needs to be modified before via useState(). I can't see in your first useEffect() when you call setItemsCart().
This question is not correct and the approach to solve the problem is not correct as well(whatever problem you are trying to solve).
React has different design.
You are trying to get the items and then set them once you get it using useEffect.
The best approach would be to pass your array as a prop from higher order component and then use your useEffect once it has been triggered by dependencies(passed prop)
Make useEffect hook run before rendering the component

Why is React state not updated inside functions? [duplicate]

This question already has answers here:
React - useState - why setTimeout function does not have latest state value?
(2 answers)
Closed 7 months ago.
I have a component that renders a table with objects. This component shows a button that, when pressed, sends the parent a specific object. The parent must set it in the state to display some graphical stuff. The rendering is working correctly, what I don't understand is why I am getting an outdated value after setting the state correctly.
It's not a race condition, React is simply ignoring the updated value of a variable, even when it re-renders the component correctly.
A minimal example:
import { useState } from "react";
import { SomeComponent } from "./SomeComponent";
export default function App() {
const [currentID, setCurrentID] = useState(null);
function getData() {
console.log("Getting data of: ", currentID); // PROBLEM: this is null
}
function setAndRetrieveData(value) {
setCurrentID(value);
// Just to show the problem and discard race conditions.
setTimeout(() => {
getData();
}, 1500);
}
return (
<div className="App">
<h1>Current ID: {currentID}</h1> {/* This works fine */}
<SomeComponent getInfoFor={setAndRetrieveData} />
</div>
);
}
SomeComponent:
export function SomeComponent(props) {
const randomID = 45;
return <button onClick={() => props.getInfoFor(randomID)}>Get info</button>;
}
Even with solutions like useStateCallback the problem persists.
Is there a way to do this without having to use the awful useEffect which is not clear when reading the code? Because the logic of the system is "when this button is pressed, make a request to obtain the information", using the hook useEffect the logic becomes "when the value of currentID changes make a request", if at some point I want to change the state of that variable and perform another action that is not to obtain the data from the server then I will be in trouble.
Thanks in advance
I think this is an issue with the way Javascript closures work.
When you execute a function, it gets bundled with all the data that pertains to it and then gets executed.
The issue is that you call this:
setTimeout(() => {
getData();
}, 1500);
inside setAndRetrieveData(value).
Even though it's inside a setTimeout, the getData() function has been bundled with the information it needs (currentID) at that point in time, not when it actually runs. So it gets bundled with the currentId before the state update takes place
Unfortunately, I would recommend using useEffect. This is the best way to ensure you avoid issues like this and any potential race conditions. Hopefully someone else can provide a different approach!
when setAndRetrieveData is called it sets a state that leads to the component being rerendered to reflect the new state. When the timeout finishes The function getData was created in the previous render. And thus only has access to the state variable from the previous render. That now is undefined.
what you could try is using a useEffect hook that that listens to changes of
currentID.
useEffect(() => {
const timeoutId = setTimeout(() => {
// Do something with the updated value
},1000);
return () => {
// if the data updates prematurely
// we cancel the timeout and start a new one
clearTimeout(timeoutId);
}
},[currentID])

How to handle useEffect being called twice in strictMode for things that should only be run once? [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
Closed 5 months ago.
This post was edited and submitted for review 5 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I understand that React calls useEffect twice in strict mode, this question is about asking what the correct way of handling it is.
I have recently hit an issue with React useEffect being called twice in strictMode. I'd like to keep strict mode to avoid issues, but I don't see any good way of making sure some specific effects are only run once. This is in a next.js environment, where I specifically want the code to only run in the client once.
For example, given the following component:
import React, { useState, useEffect } from "react";
import ky from "ky";
const NeedsToRunOnce: React.FC = () => {
const [loading, setLoading] = useState(false);
const doSomethingThatOnlyShouldHappenOnce = (data: any) => {
// Do something with the loaded data that should only happen once
console.log(`This log appears twice!`, data);
};
useEffect(() => {
if (loading) return;
console.log("This happens twice despite trying to set loading to true");
setLoading(true);
const fetchData = async () => {
const data = await ky.get("/data/json_data_2000.json").json();
setLoading(false);
doSomethingThatOnlyShouldHappenOnce(data);
};
fetchData();
}, []);
return <div></div>;
};
export default NeedsToRunOnce;
useEffect will be called twice, and both times loading will still be false. This is because strictMode makes it be called twice with the same state. The request will go through twice, and call doSomethingThatOnlyShouldHappenOnce twice. All the console.log calls above will appear twice in the console.
Since I can't modify the state to let my component know that the request has already started happening, how can I stop the get request from happening twice and then calling code that I only want to be called once? (For context, I am initialising an external library with the data loaded in the useEffect, and this library should only be initialised once).
I found a GitHub issue about this where Dan Abramov says:
Usually you’d want to have some kind of cleanup for your effect. It should either cancel your fetch or make sure you ignore its result by setting a variable inside your effect. Once you do this, there should be no actual difference in behavior.
If you cancel your fetch in effect cleanup, only one request will complete in development. However, it also doesn’t matter if another request fires. It’s being ignored anyway, and the stress-testing only happens in development.
While I agree in principle, this is tedious in practice. I've already hit two different use cases in my application where I have some library call or request that needs to only be done once in the client.
What's a reliable way of making sure that a piece of code in useEffect is only run once here? Using a state to keep track of it having already been run doesn't help, since react calls the component twice with the same state in a row.
You can use a ref to execute your useEffect once (first time or second time, as you wish), or just use a customHook. In my case, i execute the useEffect the second time. Swap true and false, to execute it the first time and not the second one.
import { useEffect, useRef } from "react";
export default function useEffectOnce(fn: () => void) {
const ref = useRef(false);
useEffect(() => {
if (ref.current) {
fn();
}
return () => {
ref.current = true;
};
}, [fn]);
}
And to use it, you can pass your callback function in param :
useEffectOnce(() => console.log("hello"));

useState keeps initial state after defining it in useEffect

I have an async function that fetches some data from an API. Since I need that data to properly load my component I decided to fetch the data in the useEffect and then store it in a useState what works just fine. However, if I try to console log it, it just shows its initial state what technically would mean it's still unset or that the console log runs before assigning the useState.
Thanks for your help in advance.
const [categories, setCategories] = useState([])
useEffect(() => {
fetchListProductCats().then(function(result){
setCategories(result.data)
})
console.log(categories) // returns -> []
}, []);
if(!categories.length){
return "loading"
}
async function with Promise, make sure you know what it means.
The console.log line in this code always runs before the Promise resolved.
If you want to observe the changing of categories, use another useEffect:
useEffect(() => {
console.log(categories);
}, [categories]);
Your state is already updated with data you wanted its showing categories with it's initial values because you have rendered useEffect with no dependancies.
And it is consoled befored being updated.
Try using below code snippets to log.
useEffect(() => {
console.log(categories)
}, [categories]);
React setState is async. It won't update immediately after calling the function. It will go through a re-rendering process first to update the user interface. Once re-rendering is done, the functions provided in the useEffect hook will be called.
useEffect(()=>{
console.log(categories)
}, [categories])

Categories

Resources