javascript - react - Reaccionar-setState() en el componente desmontado
setstate react (6)
En mi componente de reacción, estoy tratando de implementar un spinner simple mientras una solicitud ajax está en curso; estoy usando el estado para almacenar el estado de carga.
Por alguna razón, este fragmento de código a continuación en mi componente React arroja este error
Solo puede actualizar un componente montado o montado. Esto generalmente significa que llamó a setState () en un componente desmontado. Este es un no-op. Verifique el código del componente indefinido.
Si me deshago de la primera llamada setState, el error desaparece.
constructor(props) {
super(props);
this.loadSearches = this.loadSearches.bind(this);
this.state = {
loading: false
}
}
loadSearches() {
this.setState({
loading: true,
searches: []
});
console.log(''Loading Searches..'');
$.ajax({
url: this.props.source + ''?projectId='' + this.props.projectId,
dataType: ''json'',
crossDomain: true,
success: function(data) {
this.setState({
loading: false
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
this.setState({
loading: false
});
}.bind(this)
});
}
componentDidMount() {
setInterval(this.loadSearches, this.props.pollInterval);
}
render() {
let searches = this.state.searches || [];
return (<div>
<Table striped bordered condensed hover>
<thead>
<tr>
<th>Name</th>
<th>Submit Date</th>
<th>Dataset & Datatype</th>
<th>Results</th>
<th>Last Downloaded</th>
</tr>
</thead>
{
searches.map(function(search) {
let createdDate = moment(search.createdDate, ''X'').format("YYYY-MM-DD");
let downloadedDate = moment(search.downloadedDate, ''X'').format("YYYY-MM-DD");
let records = 0;
let status = search.status ? search.status.toLowerCase() : ''''
return (
<tbody key={search.id}>
<tr>
<td>{search.name}</td>
<td>{createdDate}</td>
<td>{search.dataset}</td>
<td>{records}</td>
<td>{downloadedDate}</td>
</tr>
</tbody>
);
}
</Table >
</div>
);
}
La pregunta es por qué recibo este error cuando el componente ya debería estar montado (como se llama desde componentDidMount). Pensé que era seguro establecer el estado una vez que el componente está montado.
La pregunta es por qué recibo este error cuando el componente ya debería estar montado (como se llama desde componentDidMount). Pensé que era seguro establecer el estado una vez que el componente está montado.
No se llama desde
componentDidMount
.
Su
componentDidMount
genera una función de devolución de llamada que se ejecutará en la pila del controlador del temporizador, no en la pila de
componentDidMount
.
Aparentemente, cuando se ejecuta su devolución de llamada (
this.loadSearches
), el componente se ha desmontado.
Entonces la respuesta aceptada lo protegerá. Si está utilizando alguna otra API asincrónica que no le permite cancelar funciones asincrónicas (ya enviadas a algún controlador), puede hacer lo siguiente:
if (this.isMounted())
this.setState(...
Esto eliminará el mensaje de error que informa en todos los casos, aunque se siente como barrer cosas debajo de la alfombra, particularmente si su API proporciona una capacidad de cancelación (como
setInterval
hace
clearInterval
con
clearInterval
).
Comparta una solución habilitada por los ganchos de reacción .
React.useEffect(() => {
let isSubscribed = true
callApi(...)
.catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
.then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
.catch(({ isSubscribed, ...err }) => console.error(''request cancelled:'', !isSubscribed))
return () => (isSubscribed = false)
}, [])
la misma solución puede extenderse a cualquier momento en que desee
cancelar
solicitudes anteriores sobre cambios de identificación de recuperación, de lo contrario, habría condiciones de carrera entre múltiples solicitudes en vuelo (
this.setState
fuera de servicio)
React.useEffect(() => {
let isCancelled = false
callApi(id).then(...).catch(...) // similar to above
return () => (isCancelled = true)
}, [id])
Esto funciona gracias a los closures en JavaScript.
En general, la idea anterior estaba cerca del enfoque makeCancelable recomendado por el documento react, que establece claramente
isMounted es un antipatrón
Crédito
Para la posteridad,
Este error, en nuestro caso, estaba relacionado con Reflujo, devoluciones de llamada, redireccionamientos y setState. Enviamos un setState a una devolución de llamada onDone, pero también enviamos una redirección a la devolución de llamada onSuccess. En caso de éxito, nuestra devolución de llamada onSuccess se ejecuta antes que onDone . Esto provoca una redirección antes del intento de setState . De ahí el error, setState en un componente desmontado.
Acción de la tienda de reflujo:
generateWorkflow: function(
workflowTemplate,
trackingNumber,
done,
onSuccess,
onFail)
{...
Llamar antes de arreglar:
Actions.generateWorkflow(
values.workflowTemplate,
values.number,
this.setLoading.bind(this, false),
this.successRedirect
);
Llamar después de arreglar:
Actions.generateWorkflow(
values.workflowTemplate,
values.number,
null,
this.successRedirect,
this.setLoading.bind(this, false)
);
Más
En algunos casos, dado que React''s isMounted es "obsoleto / antipatrón", hemos adoptado el uso de una variable _mounted y la supervisamos nosotros mismos.
Para quién necesita otra opción, el método de devolución de llamada del atributo ref puede ser una solución alternativa. El parámetro de handleRef es la referencia al elemento div DOM.
Para obtener información detallada acerca de las referencias y DOM: https://facebook.github.io/react/docs/refs-and-the-dom.html
handleRef = (divElement) => {
if(divElement){
//set state here
}
}
render(){
return (
<div ref={this.handleRef}>
</div>
)
}
Sin ver la función de renderizado es un poco difícil. Aunque ya puede detectar algo que debe hacer, cada vez que usa un intervalo debe borrarlo al desmontar. Asi que:
componentDidMount() {
this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}
componentWillUnmount () {
this.loadInterval && clearInterval(this.loadInterval);
this.loadInterval = false;
}
Dado que esas devoluciones de llamada de éxito y error aún pueden llamarse después de desmontar, puede usar la variable de intervalo para verificar si está montada.
this.loadInterval && this.setState({
loading: false
});
Espero que esto ayude, proporcione la función de renderizado si esto no funciona.
Aclamaciones
class myClass extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this._isMounted = true;
this._getData();
}
componentWillUnmount() {
this._isMounted = false;
}
_getData() {
axios.get(''https://example.com'')
.then(data => {
if (this._isMounted) {
this.setState({ data })
}
});
}
render() {
...
}
}