useState keeps initial state after defining it in useEffect - javascript

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

Related

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

Do something right after calling useCallback

I am calling useCallback hook and passing it data returned from an api.
The useCallback hook is formatting the returned data and setting it in the state
I expect updated data right after calling the useCallback hook.
My question is, will I get updated data in the very next line after calling useCallback hook? Moreover, what can I do to ensure that I do get updated data?
Here is how the code looks like:
const [data, setData] = React.useState([]);
const processPeople = React.useCallback((peopleData) => {
// we will do a lot of formatting here on peopleData and then will do:
setData(peopleData)
}, []}
const loadOptions = React.useCallback((callback) => {
getPeopleData({ // calling API
variables: {
perPage: 20,
page: 1,
},
}).then((response) => {
processPeople(response) // This is a lengthy callback which processes response
console.log(data) // How to make sure I get updated data here?
// callback(data) This is a built-in method of a library which requires updated data
// Please see loadOptions is a library's method
}
}, [data] }
return(
<SingleAsyncSelect
options={peopleDataOptions}
getOptions={loadOptions}
selectOption={handleAdd}
/>
)
The quick answer is "no".
Is there a reason you are not calling setData directly instead of wrapping it within processPeople?
Please note that you must ensure that your useCallbacks use the same deps; otherwise, you will have unsynced values; i.e data. Technically, this should not be a problem in your example in processPeople, which receives the value by the invoker.
For the main question, which primarily focuses on this couple of lines:
processPeople(response) // This is a lengthy callback which processes response
console.log(data) // Will I get updated data here?
No, data will not get updated right away as setState is async. Data's value is received from your useCallbacks deps; i.e the last line in your code snippet.
I am not entirely sure what you are trying to do, but as long as you pass in the variables you need to you hooks' deps, you will get the updated values. Again, it is async, but in the next render, you will get the updated value.

Do functions get the latest state value in React?

I have a function inside of my functional component that uses a value saved in state. However, when it is called, it has the original value in state, not the updated value. When I look at my component in Chrome React Dev Tools, I see that the updated value is stored in state. Aren't functions supposed to get the latest state value in React? I didn't think I'd have to wrap my functions in a useEffect every time some value in state they depend on changes. Why is this happening?
const Editor = (props) => {
const [template, setTemplate] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
useEffect(() => {
if (props.templateId) {
getTemplate(props.templateId));
}
},[]);
const getTemplate = (templateId) => {
{...make API to get template...}
.then((response) => {
if (response.template) setTemplate(response.template);
});
}
/* THIS FUNCTION SAYS TEMPLATE IS ALWAYS NULL */
const sendClick = async () => {
if (template) {
await updateTemplate();
} else {
await initializeTemplate();
}
setOpenDialog(true);
};
}
UPDATE: I figured out the issue. The sendClick function is being used inside an object that I have in state. When that object is created, it creates a version of the sendClick function based on the state at that time. I realized I needed to refactor my code so that the function is not stored within my object in state so that the function will always have the latest state values.
Please correct the code there its setTemplate(template)); not getTemplate(template));
I'm guessing that you have that right in the source code... if Yes then,
You have got into a trap that all developers new to React fall into.
This code is betraying you ...
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[]); // Here is where you make the mistake
The second argument you pass to the useEffect is called as Dependencies. Meaning if your useEffect is dependent on any state or any variable or function, Ii should be pass as the second argument inside the []. By now you should have got the answer.
Clearly, your useEffect is dependent on template. You should pass that inside the [].
So the code will be : -
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[template]);
Now React will automatically run the function every time the value of template changes therefore, updates template.
For more information about useEffect ...
Refer React Documentation
Refer the useEffect API

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