UseEffect dosen't trigger setState when using sessionStorage - javascript

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

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.

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])

Triggering useEffect only certain conditions

I have basic understanding of useEffect. Without second parameter (dependency array) it runs on every render. With empty array, it runs on first render. With parameters in array, it runs whenever some of parameters changes.
Say I have useEffect with two dependencies (from GraphQL query): result.data and result.loading. I want useEffect to run if result.data changes, and result.loading is false. Purpose is for example to update Redux store:
useEffect(() => {
if (result.loading) return;
dispatch(updatePhotos([...photos, ...result.data.photos]));
}, [result.data, result.loading]);
But there's a catch: I have to include photos to list of dependencies. However, photos variable will be updated in other place, and it triggers this useEffect again.
How can I run useEffect only when those two variables changes?
I can of course use useState to store variable resultFetched, set it to true in useEffect and then dispatch only if it is false. But at some point I have to change it back to true, and useEffect runs again, since I can't manually change result.data or result.loading.
I'm lost how to properly use useEffect in these situations when there is lots of variables to handle.
Currently I'm building infinite scrolling photo list, where list is loaded part by part via GraphQL. But when user opens some photo and eventually returns to photo list, it is restored from Redux to same state and scroll position as it was before opening the photo.
I have spent countless hours trying to get it work, but this useEffect-thing is spoiling my every attempt. :) They always gets triggered before I want them to trigger, because there is so many changing variables.
Also, sometimes I want to run a function within useEffect (function added to dependency array), and I use useCallback for that function to memoize it. But then I also have to add all variables that function uses to dependency array of that useCallback, so function gets regenerated when those variables changes. That means that useEffect suddenly runs again, because the function in dependency array changes.
Is there really no way to use functions/variables in useEffect, without them to trigger useEffect?
It all depends on how updatePhotos works. If that creates an action then the problem is you are creating the new state in the wrong place. The previous value of photos shouldn’t be used here because as you pointed out, that causes a dependency.
Instead your reducer will have the old value of photos you can use and you simply pass the new request data to your reducer.
Described in more detail here: https://overreacted.io/a-complete-guide-to-useeffect/#decoupling-updates-from-actions
You can have two separate useEffect functions inside the same component and they will work independent one of another. use one for photos and one for data loading. I hope this example helps you to wrap your head around this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
useEffect(() => {
const id = setInterval(() => {
setCount2((c) => c + step);
}, 1500);
return () => clearInterval(id);
}, [step]);
return (
<div>
<h1>{count}</h1>
<h1>{count2}</h1>
<input value={step} onChange={(e) => setStep(Number(e.target.value))} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
Please refer to this example in sandbox
https://codesandbox.io/s/react-playground-forked-6h0oz

How to fetch data without useEffect hooks in React function component?

I know the conventional way when using hooks is to fetch the data using the useEffect hook. But why can't I just call axios in the functional component instead of a hook and then set the data.
Basically, I am asking what is wrong with doing this:
const [users, setUsers] = useState(null);
axios.get("some api call")
.then(res => setUsers(res.data))
Here, I do not use useEffect, what could go wrong?
Making a request and changing the state on render like that will cause the component to re-render forever.
Every time the component renders, e.g. due to props changing or due to hooks changing, that axios.get request gets called. When it gets a response, it will update the state. Then, because the state has changed, the component re-renders, and axios.get is called again. And the state changes again, and the request is made again, forever.
Prefer useEffect(() => code... , []).
That said, you can also do it while avoiding an infinite loop but it's a very bad practice and I don't recommend it.
Yes, you will have a re-render but you won't have an infinite loop. Use useState's lazy init function.
const [users, getUsers] = useState(() => {
axios.get("some api call")
.then(res => getUsers(res.data))
});
Best practice is :
const [users,getUsers]= useState();
useEffect ( () => {
axios.get("some api call")
.then(res=>getUsers(res.data))
}, []);

How to avoid setState in useEffect hook causing second render

I'm writing a custom hook that takes an id as input and then should do the following tasks:
get data for that id synchronously from a store
subscribe to changes in this store and update data accordingly
I came up with the following implementation which has the downside that I am setting state directly from inside the effect hook, causing a second render.
function useData({id}) {
// set initial data on first render
const [data, setData] = useState(Store.getData(id));
useEffect(() => {
// when id changes, set data to whatever is in store at the moment
// here we call setData directly from inside useEffect, causing a second render
setData(Store.getData(id));
// subscribe to changes to update data whenever it changes in the store
return Store.onChange(id, setData);
}, [id]);
return data;
}
A second approach I tried was to add a dummy state that is only there to cause a re-render. In this approach I am directly returning the data received from Store.getData(). This ensures that I will get the freshest data with every render and the useEffect ensures that every onChange trigger will cause a new render.
function useData({id}) {
// adding some dummy state that we only use to force a render
const [, setDummy] = useState({});
const refresh = useCallback(() => {
setDummy({});
}, []);
useEffect(() => {
// subscribe to changes cause a refresh on every change
return Store.onChange(id, refresh);
}, [id, refresh]);
return Store.getData[id];
}
The second approach works well but it feels weird to add this dummy state. Sure, I could put this into another useRefresh hook but I am not sure if this would really be a good practice.
Is there any better way of implementing this, without calling setData directly from inside useEffect and without relying on some unused dummy state?
So by now you use useState inside your hook just to re-trigger rendering of host component once store is changed. How about taking change handler from the outside?
function useData(id, onChanged) {
useEffect(() => {
return store.onChange(id, onChanged);
}, [id, onChanged]);
return store.getData(id);
}

Categories

Resources