Tengo un ejercicio de React sobre el uso de estados en componentes de clase. Mi solución funciona bien cuando codifico de la siguiente manera:
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>
Pero cuando lo escribo de otra manera, no obtengo ningún error pero tampoco salida.
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>
¿Cuál es el problema de la segunda manera?
setState
es asíncrono. this.state
obtiene el nuevo valor en el siguiente renderizado, pero está tratando de usarlo de inmediato.
Si desea almacenar el mensaje en el estado, puede hacerlo en las devoluciones de llamada pasadas a getCurrentPosition
y es mejor manejar todo eso en ComponentDidMount
en lugar del 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>; } }
Dos cuestiones principales:
1) setState
es asíncrono
Incluso si el segundo problema no existiera, no puede introspeccionar inmediatamente el estado inmediatamente después de llamar a setState
; el estado aún no se habrá establecido.
2) getCurrentPosition
es asíncrono
Las devoluciones de llamada para getCurrentPosition
se ejecutarán en un momento indeterminado en el futuro. El constructor continúa ejecutándose, llamando a setState
(basado en el estado actual, inicializado a nulos y cadenas vacías) antes getCurrentPosition
probablemente haya llamado a sus devoluciones de llamada.
(Y no hay ninguna razón para llamar a setState
en el propio constructor (a diferencia de las devoluciones de llamada a getCurrentPosition
, que está bien), puede establecer el estado directamente como lo hace cuando lo inicializa por primera vez.