user17060738
user17060738

Reputation:

Setting a setState to another state value in REACT

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

Answers (2)

Ramesh Reddy
Ramesh Reddy

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.

Edit agitated-pond-rv6u2

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

Dave Newton
Dave Newton

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

Related Questions