I'm trying to create a weather app in React. I want to toggle the app language using onClick
.
Here's my code so far.
import React, { Component } from "react";
import axios from "axios";
import "./App.css";
class App extends Component {
componentDidMount() {
this.setState({
isLoading: true
});
axios
.get("path to weather api")
.then(res => {
console.log(res.data.data[0]);
const { city_name, temp, weather } = res.data.data[0];
this.setState({
loc: city_name,
temp: temp,
code: weather.code,
isLoading: false
});
this.setState({
desc: this.convertCode(this.state.code)
});
});
}
switchLanguage = () => {
if (this.state.lang === "en") {
this.setState({
lang: "hi",
desc: this.convertCode(this.state.code)
});
} else {
this.setState({
lang: "en",
desc: this.convertCode(this.state.code)
});
}
};
convertCode = givenCode => {
if (this.state.lang === "en") {
if (
givenCode === 200 ||
givenCode === 201 ||
givenCode === 202 ||
givenCode === 230 ||
givenCode === 231 ||
givenCode === 232 ||
givenCode === 233 ||
givenCode === "200" ||
givenCode === "201" ||
givenCode === "202" ||
givenCode === "230" ||
givenCode === "231" ||
givenCode === "232" ||
givenCode === "233"
) {
return "Thunderstorms";
} else if (
givenCode === 300 ||
givenCode === 301 ||
givenCode === 302 ||
givenCode === "300" ||
givenCode === "301" ||
givenCode === "302"
) {
return "Drizzle";
}
..............
..............
IF CONDITION FOR THE OTHER LANGUAGE
};
render() {
if (!this.state.isLoading) {
return (
<div className="App">
<div className="container">
<div className="languageSwitcher">
<i className="fa fa-language" onClick={this.switchLanguage} />
</div>
<div className="location">
<i className="fa fa-location-arrow" /> {this.state.loc}
</div>
{this.state.lang === "en" && (
<div className="temperature">It's {this.state.temp} degrees.</div>
)}
{this.state.lang === "hi" && (
<div className="temperature">
तापमान {this.state.temp} डिग्री है।
</div>
)}
<div className="description">{this.state.desc}</div>
</div>
</div>
);
} else {
return <div className="loading">Fetching weather data...</div>;
}
}
}
export default App;
Everything works except for the div
with className="desc"
. desc
is always one phase behind. I mean when state.lang
is en
, it displays the text in hi
and vice-versa.
I just started learning React so the code's pretty messed up. Sorry about that.
Thanks.
You have two state management issues and one flow-of-execution issue, one (or possibly more) of which is causing the behavior you mention (but all need fixing in any case):
State updates are asynchronous. This means that this.state
does not have the updated state immediately after a this.setState
call.
Because state updates are asynchronous, if you're setting state based on existing state (which you are in a few places, including switchLanguage
), you must use the version of setState
you pass a callback to, not the version you pass an object to; in the callback, use the up-to-date state object the callback receives as a parameter.
When you do this.setState({/*...*/desc: this.convertCode(/*...*/)})
, you're calling convertCode
before calling setState
, and passing its return value into setState
as a value on a property on the object you're passing it. So even if it weren't for issue #2 above, it would still have a basic flow-of-control issue and convertCode
would still see the about-to-become-out-of-date this.state.lang
.
The best way to address all this is probably to update convertCode
to optionally accept the lang
to use (defaulting to this.state.lang
):
convertCode = (givenCode, lang = this.state.lang) => {
// ...use `lang`, not `this.state.lang`...
...and then address the various issues setting state and using convertCode
. The first is in componentDidMount
:
componentDidMount() {
this.setState({
isLoading: true
});
axios
.get("path to weather api")
.then(res => {
console.log(res.data.data[0]);
const { city_name, temp, weather } = res.data.data[0];
this.setState({
loc: city_name,
temp: temp,
code: weather.code,
isLoading: false
});
this.setState({
desc: this.convertCode(this.state.code) // <=== Error is here
});
});
}
this.state.code
won't have been updated yet, because state updates are asynchronous. Also, we want to use this.state.lang
, so we need to use the callback form. Instead, combine those two calls and pass the lang to convertCode
:
this.setState(prevState => ({
loc: city_name,
temp: temp,
code: weather.code,
isLoading: false,
desc: this.convertCode(weather.code, prevState.lang),
}));
In switchLanguage
, both problems #2 and #3 exist:
// INCORRECT:
// A) Sets state based on state without callback
// B) Calls `convertCode` before `setState`
switchLanguage = () => {
if (this.state.lang === "en") {
this.setState({
lang: "hi",
desc: this.convertCode(this.state.code)
});
} else {
this.setState({
lang: "en",
desc: this.convertCode(this.state.code)
});
}
};
We can fix both issues by using the callback form and passing the language to use into convertCode
:
// Uses callback when setting state based on state
switchLanguage = () => {
this.setState(prevState => {
const lang = prevState.lang === "en" ? "hi": "en";
return {lang, desc: this.convertCode(prevState.code, lang)};
});
};
Notice the use of prevState
both for the lang
check and for passing code
to this.convertCode
.