Reputation:
I have a React exercise about using states in class components. My solution works well when I code as below:
import React from "react";
import "./App.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { lat: null, errorMessage: "" };
window.navigator.geolocation.getCurrentPosition(
(position) => this.setState({ lat: position.coords.latitude }),
(err) => this.setState({ errorMessage: err.message })
);
}
render() {
if (this.state.lat && !this.state.errorMessage) {
return <div>Latitude: {this.state.lat}</div>;
} else if (!this.state.lat && this.state.errorMessage) {
return <div>Erroe: {this.state.errorMessage}</div>;
} else {
return <div>Latitude: Loading...</div>;
}
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
But when I write it in another way, I get no error but no output
class App extends React.Component {
constructor(props) {
super(props);
this.state = { lat: null, errorMessage: "", message: "" };
window.navigator.geolocation.getCurrentPosition(
(position) => this.setState({ lat: position.coords.latitude }),
(err) => this.setState({ errorMessage: err.message })
);
if (this.state.lat && !this.state.errorMessage) {
this.setState({ message: "Latitude: " + this.state.lat });
} else if (!this.state.lat && this.state.errorMessage) {
this.setState({ message: "Error: " + this.state.errorMessage });
} else {
this.setState({ message: "Loading..." });
}
}
render() {
return <div> {this.state.message} </div>;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
What is the problem by the second way?
Upvotes: 0
Views: 2403
Reputation: 10662
setState
is async. this.state
gets the new value in the next render but you're trying to use it right away.
If you want to store the message in the state, you can do that in the callbacks passed to getCurrentPosition
and it is better to handle all that in ComponentDidMount
rather than the constructor.
import { Component } from "react";
import "./styles.css";
export default class App extends Component {
constructor(props) {
super(props);
this.state = { lat: null, errorMessage: "", message: "" };
}
componentDidMount() {
window.navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
this.setState({ lat, message: `Latitude: ${lat}` });
},
(err) =>
this.setState({
errorMessage: err.message,
message: `Error: ${err.message}`
})
);
}
render() {
return <div> {this.state.message} </div>;
}
}
Upvotes: 1
Reputation: 160321
Two main issues:
1) setState
is asynchronous
Even if the second problem didn't exist you cannot immediately introspect state immediately after calling setState
; the state won't have been set yet.
2) getCurrentPosition
is asynchronous
The callbacks for getCurrentPosition
will execute at an indeterminate time in the future. The constructor continues to run, calling setState
(based on current state, initialized to nulls and empty strings) before getCurrentPosition
has likely called its callbacks.
(And there's zero reason to call setState
in the constructor itself (as opposed to the callbacks to getCurrentPosition
, which is fine), you can just set state directly as you do when you first initialize it.
Upvotes: 0