简体   繁体   中英

Updates to the state of React context don't re-render children

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

    }, []);}

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 ...
}

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.

Docs: https://reactjs.org/docs/hooks-effect.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM