I'm updating the shared state in context through a callback in the child component, but that does not cause a re-render, which results in the context in the child component having an initial value until the next re-render.
Is there a way to force the update of the child and a re-render once state is updated in the context provider?
My context provider:
const UserLocationContext = React.createContext()
export const useUserLocation = () => {
return useContext(UserLocationContext)
}
export const UserLocationProvider = ({ children }) => {
const [ipUserLocation, setIpUserLocation] = useState(null)
const updateIpUserLocation = (ipUserLocation) => {
setIpUserLocation(ipUserLocation)
console.log(ipUserLocation) //value is updated here immediately after the updateIpUserLocation call
}
return (
<UserLocationContext.Provider value = {{ipUserLocation, updateIpUserLocation}}>
{children}
</UserLocationContext.Provider>
)
}
export default UserLocationProvider
Child:
const LocationHandler = () => {
const {ipUserLocation, updateIpUserLocation} = useUserLocation()
useEffect(() => {
const ip_url = `https://api.freegeoip.app/json/`
const fetchIPLocation = async () => {
const result = await fetch(ip_url);
const json = await result.json();
updateIpUserLocation([json.latitude, json.longitude])
console.log(ipUserLocation) //value here remains null until next re-render
}
fetchIPLocation()
}, []);}
CodePudding user response:
The problem is useState
is asynchronous, so ipUserLocation
value is not updated immediately after setIpUserLocation
gets called.
For the fix, you can add ipUserLocation
as a dependency to useEffect
that would help you to listen to all changes from ipUserLocation
on LocationHandler
.
const LocationHandler = () => {
const {ipUserLocation, updateIpUserLocation} = useUserLocation()
useEffect(() => {
const ip_url = `https://api.freegeoip.app/json/`
const fetchIPLocation = async () => {
const result = await fetch(ip_url);
const json = await result.json();
updateIpUserLocation([json.latitude, json.longitude])
}
fetchIPLocation()
}, []);}
//add another `useEffect` with `ipUserLocation` in dependencies
useEffect(() => {
//TODO: You can do something with updated `ipUserLocation` here
console.log(ipUserLocation)
}, [ipUserLocation])
return ...
}
CodePudding user response:
Actually the child component gets re-rendered when you update the state of the context. And this happens because you are using the useContext hook to listen to any change made to the context. What you can do to actually prove to yourself that the child gets re-rendered is adding this in the child component:
useEffect(() => {
console.log(ipUserLocation);
}, [ipUserLocation])
With this useEffect the console.log will run everytime the child gets re-rendered and the ipUserLocation has changed.