Reputation: 2290
I have a createContext
component that is using useState
to set multiple values returned from a fetch
function. However in my code below, when a state is updated, the others states return to the original value.
For example, in getCountryCode()
the state is updated for countryCode
, but then iconCode
in weatherInit()
fetches its value and countryCode
returns to the original US
.
import React, { createContext, useState, useEffect } from 'react';
export const GlobalConsumer = createContext();
export const GlobalProvider = ({ children }) => {
const [state, setState] = useState({
menuPanel: false,
countryCode: 'US',
weatherLoading: true,
iconCode: '',
fahrenheit: '',
celcius: '',
showCelcius: false
});
const getCountryCode = () => {
const url = `https://ipapi.co/json/`;
fetch(url)
.then(data => data.json())
.then(data => {
const countryCode = data.country;
setState({ ...state, countryCode });
});
};
const weatherInit = () => {
const CITY_LAT = '...';
const CITY_LON = '...';
const OW_KEY = '...';
const url = `//api.openweathermap.org/data/2.5/weather?lat=${CITY_LAT}&lon=${CITY_LON}&units=imperial&appid=${OW_KEY}`;
fetch(url)
.then(data => data.json())
.then(data => {
const iconCode = data.weather[0].id;
setState({ ...state, iconCode });
const fahrenheit = Math.round(data.main.temp_max);
setState({ ...state, fahrenheit });
const celcius = Math.round((5.0 / 9.0) * (fahrenheit - 32.0));
setState({ ...state, celcius });
setTimeout(() => {
setState({ ...state, weatherLoading: false });
}, 150);
});
};
useEffect(() => {
getCountryCode();
weatherInit();
}, []);
return (
<GlobalConsumer.Provider
value={{
contextData: state,
togglemMenuPanel: () => {
setState({ ...state, menuPanel: !state.menuPanel });
},
toggleCelcius: () => {
setState({ ...state, showCelcius: !state.showCelcius });
}
}}
>
{children}
</GlobalConsumer.Provider>
);
};
I believe that this is caused because each value
requires it's own useState
. However, can these values be merged or is there another way to achieve this outcome, where I am only required to pass as data
to the Provider context?
Upvotes: 1
Views: 3101
Reputation: 1572
It's because you are using the old value of state
when calling setState()
. As documented here (Scroll down to the "Note"-block) you have to pass a function to your setState
call:
const iconCode = data.weather[0].id;
setState(prevState => ({ ...prevState, iconCode }));
const fahrenheit = Math.round(data.main.temp_max);
setState(prevState => ({ ...prevState, fahrenheit }));
const celcius = Math.round((5.0 / 9.0) * (fahrenheit - 32.0));
setState(prevState => ({ ...prevState, celcius }));
setTimeout(() => {
setState(prevState => ({ ...prevState, weatherLoading: false }));
}, 150);
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Upvotes: 2