React how to use state in passed function - reactjs

I am using React context to pass down state. When my state changes, it also changes in the child Component (console logs shows new value), but when this state is used in a function, it doesnt update there (console.log shows old value).
Do I need to rerender the function? How?
const {user, userInfo, ref} = useSession(); <-- wrapper for useContext
console.log(userInfo); <--- correct, updated value
const haalDataOp = async () => {
console.log(userInfo.enelogic); <--- old value displaying
...
}
I am using the function haalDataOp from a button (onClick)
As someone already mentioned, I could use useRef, but I dont understand why. Why does this simple example work (extracted from https://dev.to/anpos231/react-hooks-the-closure-hell-71m), and my code doesnt:
const [value, setValue] = useState(1);
const handleClick = useCallback(
() => {
setValue(value + 1)
},
[value],
);
I also tried using useCallback (with userInfo in the dep array) in my example but that doesnt do the trick.

const ... userInfo ... is a constant, so in a Component like following:
console.log('render', userInfo.enelogic) // different value in each render
const haalDataOp = async () => {
console.log('before', userInfo.enelogic) // correct old value
await update()
console.log('after', userInfo.enelogic) // still the same old value
}
return <button onClick={haalDataOp} />
...it would log:
render old
before old
after old
render new
...because the userInfo inside haalDataOp is a closure referencing to the value from the original render. If you need to access a mutable reference that would point to the up-to-date value from a future render instead, you can useRef:
const userInfoRef = useRef()
userInfoRef.current = userInfo
console.log('render', userInfo.enelogic) // different value in each render
const haalDataOp = async () => {
console.log('before', userInfoRef.current.enelogic) // old value
await update()
console.log('after', userInfoRef.current.enelogic) // should be new value
}
return <button onClick={haalDataOp} />
However, there might be a race condition and/or the execution of the 'after' code happens deterministically BEFORE the next render, in which case you will need to use some other trick...
I suspect that the const {ref} = useSession() is needed for exactly this situation, so please read the documentation.

Related

usestate can change the state value after axios in useEffect

I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);

React set variable value from a call back from child component

I am learning React and need some help in finding out a problem with this code and understanding what am I not doing incorrectly. Parent component is MyMap and it needs values for Lat from its child component which is Search bar.
Call back function name is setLatInfo and it will set the value for the lat variable defined in useState.
The error that I am seeing is Assignments to the 'setLatInfo' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.
on the function setLatInfo = (latInfo: number)
How can I assign a call back function within an useEffect block?
import SearchBar from "../components/SearchBar";
const MyMap: FunctionComponent = () => {
const [lat, setLat] = useState();
let setLatInfo: (latInfo: number) => void;
useEffect(() => {
const loadData = () => {
// map?.map
// (map as any)?.map.addLayer(h3Layer);
};
setLatInfo = (latInfo: number) => { // this entire function is showing error
setLat(latInfo);
console.log(lat);
console.log(latInfo);
};
}, []);
return (
<div>
<SearchBar
parentCallbackLat={setLatInfo}
/>
</div>
);
};
Best to check out the usCallback syntax and how it works, as well as some examples so you can understand exactly what's happening with it. A basic example for your use case would be:
const [lat, setLat] = useState();
const setLatInfo = useCallback(latInfo => {
console.log(latInfo);
setLat(latInfo);
},[setLat]);
useEffect(() => {
setLatInfo("initial value");
}, [setLatInfo]);
return <SearchBar parentCallbackLat={setLatInfo} />;
Also, just so you know React state changes are async and you can't print their new values on the next line

useEffect re-renders too many times

I have this component, that needs to fetch data, set it to state and then pass it to the children.
Some of the data also needs to be set in context.
My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute.
I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing.
At the moment the array is containg the set...functions to prevent eslint to throw warnings.
Is there a better way to avoid this many re-renders ?
const Home = (props) => {
console.log("TCL: Home -> props", props);
const classes = useStyles();
const [value, setValue] = React.useState(0);
//CONTEXT
const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
const {
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
} = useContext(UserContext);
// STATE
const [searchSettings, setSearchSettings] = useState([]);
const [oppData, setOppData] = useState([]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = index => {
setValue(index);
};
//API CALLS
useEffect(() => {
const triggerAPI = async () => {
setIsFullView(false);
const oppResponse = await API.getOpportunity();
if(oppResponse){
setOppData(oppResponse.response);
}
const profileResponse = await API.getUserProfile();
if(profileResponse){
setUserName(profileResponse.response.first_name);
setUserLastName(profileResponse.response.last_name);
setUserEmail(profileResponse.response.emailId);
}
const profileExtData = await API.getUserProfileExt();
if(profileExtData){
setAvatarProfile(profileExtData.response.avatar);
setListSavedJobs(profileExtData.response.savedJobs);
setSearchSettings(profileExtData.response.preferredIndustry);
}
};
triggerAPI();
}, [
setOppData,
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
setListSavedJobs,
setIsFullView,
]);
...```
Pass just an empty array to second parameter of useEffect.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
Source
Edit: Try this to avoid rerenders. Use with caution
Only Run on Mount and Unmount
You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.
Prevent useEffect From Running Every Render
If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)
const [value, setValue] = useState('initial');
useEffect(() => {
// This effect uses the `value` variable,
// so it "depends on" `value`.
console.log(value);
}, [value])
For more clarification useEffect
If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html

React mutable reference, update state in function

I have much trouble understanding state and mutability in react functions. I asked a question before but wanted to ask a new question because I am not understanding what the underlying issue is.
I have the following code:
const {userInfo} = useSession(); //<-- wrapper for useContext, gives some object with user info
console.log(userInfo); //<--- correct, updates every time
const haalDataOp = async () => {
console.log(userInfo.enelogic); //<--- old value displaying, never updates
}
return <button onClick={haalDataOp} />
I have a mechanism where I listen to changes in userInfo object (in Firestore) and update the context accordingly. So when I update my userinfo object, I see the correct value logging (line 2). But, the function (which was called through a button click) has an old value of userInfo. So when I click a button which triggers haalDataOp it console.logs the old userInfo value.
In the old question, a guy suggested to use useRef. Why is that? His example worked, but I am not understanding why I should use useRef here because below snippet works (and doesnt need useRef):
const [value, setValue] = useState(1);
const handleClick = () => {
console.log(value);
setValue(value + 1)
}
const [value, setValue] = useState(1);
const handleClick = () => {
console.log(value);
setValue(value + 1)
}
is a good approach. You can use either useState or useRef, they are very similar, with the difference that useState triggers a re-render. useRef is useful when you want to keep an instance variable around (react doc)

How to wait for multiple state updates in multiple hooks?

Example
In my scenario I have a sidebar with filters.. each filter is created by a hook:
const filters = {
customerNoFilter: useFilterForMultiCreatable(),
dateOfOrderFilter: useFilterForDate(),
requestedDevliveryDateFilter: useFilterForDate(),
deliveryCountryFilter: useFilterForCodeStable()
//.... these custom hooks are reused for like 10 more filters
}
Among other things the custom hooks return currently selected values, a reset() and handlers like onChange, onRemove. (So it's not just a simple useState hidden behind the custom hooks, just keep that in mind)
Basically the reset() functions looks like this:
I also implemented a function to clear all filters which is calling the reset() function for each filter:
const clearFilters = () => {
const filterValues = Object.values(filters);
for (const filter of filterValues) {
filter.reset();
}
};
The reset() function is triggering a state update (which is of course async) in each filter to reset all the selected filters.
// setSelected is the setter comming from the return value of a useState statement
const reset = () => setSelected(initialSelected);
Right after the resetting I want to do stuff with the reseted/updated values and NOT with the values before the state update, e.g. calling API with reseted filters:
clearFilters();
callAPI();
In this case the API is called with the old values (before the update in the reset())
So how can i wait for all filters to finish there state updated? Is my code just badly structured? Am i overseeing something?
For single state updates I could simply use useEffect but this would be really cumbersome when waiting for multiple state updates..
Please don't take the example to serious as I face this issue quite often in quite different scenarios..
So I came up with a solution by implementing a custom hook named useStateWithPromise:
import { SetStateAction, useEffect, useRef, useState } from "react";
export const useStateWithPromise = <T>(initialState: T):
[T, (stateAction: SetStateAction<T>) => Promise<T>] => {
const [state, setState] = useState(initialState);
const readyPromiseResolverRef = useRef<((currentState: T) => void) | null>(
null
);
useEffect(() => {
if (readyPromiseResolverRef.current) {
readyPromiseResolverRef.current(state);
readyPromiseResolverRef.current = null;
}
/**
* The ref dependency here is mandatory! Why?
* Because the useEffect would never be called if the new state value
* would be the same as the current one, thus the promise would never be resolved
*/
}, [readyPromiseResolverRef.current, state]);
const handleSetState = (stateAction: SetStateAction<T>) => {
setState(stateAction);
return new Promise(resolve => {
readyPromiseResolverRef.current = resolve;
}) as Promise<T>;
};
return [state, handleSetState];
};
This hook will allow to await state updates:
const [selected, setSelected] = useStateWithPromise<MyFilterType>();
// setSelected will now return a promise
const reset = () => setSelected(undefined);
const clearFilters = () => {
const promises = Object.values(filters).map(
filter => filter.reset()
);
return Promise.all(promises);
};
await clearFilters();
callAPI();
Yey, I can wait on state updates! Unfortunatly that's not all if callAPI() is relying on updated state values ..
const [filtersToApply, setFiltersToApply] = useState(/* ... */);
//...
const callAPI = () => {
// filtersToApply will still contain old state here, although clearFilters() was "awaited"
endpoint.getItems(filtersToApply);
}
This happens because the executed callAPI function after await clearFilters(); is is not rerendered thus it points to old state. But there is a trick which requires an additional useRef to force rerender after filters were cleared:
useEffect(() => {
if (filtersCleared) {
callAPI();
setFiltersCleared(false);
}
// eslint-disable-next-line
}, [filtersCleared]);
//...
const handleClearFiltersClick = async () => {
await orderFiltersContext.clearFilters();
setFiltersCleared(true);
};
This will ensure that callAPI was rerendered before it is executed.
That's it! IMHO a bit messy but it works.
If you want to read a bit more about this topic, feel free to checkout my blog post.

Resources