javascript - react - Advertencia: setState(...): no se puede actualizar durante una transición de estado existente con redux/inmutable
immutable js redux (5)
Estoy recibiendo el error
Advertencia: setState (...): no se puede actualizar durante una transición de estado existente (como dentro del
render
o el constructor de otro componente). Los métodos de renderizado deben ser una función pura de puntales y estado; los efectos secundarios del constructor son un anti-patrón, pero se pueden mover acomponentWillMount
.
Encontré la causa para ser
const mapStateToProps = (state) => {
return {
notifications: state.get("notifications").get("notifications").toJS()
}
}
Si no devuelvo las notificaciones allí, funciona. Pero ¿por qué es eso?
import {connect} from "react-redux"
import {removeNotification, deactivateNotification} from "./actions"
import Notifications from "./Notifications.jsx"
const mapStateToProps = (state) => {
return {
notifications: state.get("notifications").get("notifications").toJS()
}
}
const mapDispatchToProps = (dispatch) => {
return {
closeNotification: (notification) => {
dispatch(deactivateNotification(notification.id))
setTimeout(() => dispatch(removeNotification(notification.id)), 2000)
}
}
}
const NotificationsBotBot = connect(mapStateToProps, mapDispatchToProps)(Notifications)
export default NotificationsBotBot
import React from "react"
class Notifications extends React.Component {
render() {
return (
<div></div>
)
}
}
export default Notifications
ACTUALIZAR
Sobre la depuración adicional encontré que, lo anterior puede no ser la causa raíz después de todo, puedo hacer que las notificaciones se queden pero necesito eliminar el dispatch(push("/domains"))
mi redirección.
Así es como inicio de sesión:
export function doLogin (username, password) {
return function (dispatch) {
dispatch(loginRequest())
console.log("Simulated login with", username, password)
setTimeout(() => {
dispatch(loginSuccess(`PLACEHOLDER_TOKEN${Date.now()}`))
dispatch(addNotification({
children: "Successfully logged in",
type: "accept",
timeout: 2000,
action: "Ok"
}))
dispatch(push("/domains"))
}, 1000)
}
}
Me parece que el envío causa la advertencia, pero ¿por qué? Mi página de dominios no tiene mucho actualmente:
import {connect} from "react-redux"
import DomainsIndex from "./DomainsIndex.jsx"
export default connect()(DomainsIndex)
DomainsIndex
export default class DomainsIndex extends React.Component {
render() {
return (
<div>
<h1>Domains</h1>
</div>
)
}
}
ACTUALIZACIÓN 2
Mi App.jsx
. <Notifications />
es lo que muestra las notificaciones
<Provider store={store}>
<ConnectedRouter history={history}>
<Layout>
<Panel>
<Switch>
<Route path="/auth" />
<Route component={TopBar} />
</Switch>
<Switch>
<Route exact path="/" component={Index} />
<Route path="/auth/login" component={LoginBotBot} />
<AuthenticatedRoute exact path="/domains" component={DomainsPage} />
<AuthenticatedRoute exact path="/domain/:id" component={DomainPage} />
<Route component={Http404} />
</Switch>
<Notifications />
</Panel>
</Layout>
</ConnectedRouter>
</Provider>
La razón por la que esto sucede es cuando llamas al doLogin
, si lo llamas desde un constructor
. Si este es el caso, intente moverlo a componentWillMount
aunque debe llamar a este método desde un botón o ingresar hit en el formulario de inicio de sesión.
Esto ha sido documentado en el constructor no debería mutar Si esta no es la raíz del problema, importa comentar cada línea en doLogin para saber exactamente qué línea da el problema de estado, mi suposición sería el push
o el addNotification
Su dispatch(push(''/domains''))
viene junto con otros despachos que configuran el estado de un componente conectado (presumiblemente uno que se preocupa por las notificaciones) que se vuelve a montar / desmontar después de que la push
vigencia.
Como solución temporal y como prueba de concepto, intente aplazar la llamada de dispatch(push(''/domains''))
con un setTimeout
segundo cero anidado. Esto debería ejecutar el push
después de que termine cualquiera de las otras acciones (es decir, con suerte una renderización):
setTimeout(() => dispatch(push(''/domains'')), 0)
Si eso funciona, entonces es posible que desee reconsiderar su estructura de componentes. Supongo que Notifications
es un componente que desea montar una vez y mantenerlo allí durante toda la vida de la aplicación. Intente evitar remontarlo colocándolo en un lugar más alto en el árbol de componentes y convirtiéndolo en un PureComponent
(aquí están los documentos ). Además, si la complejidad de su aplicación aumenta, debe considerar el uso de una biblioteca para manejar la funcionalidad asíncrona, como redux-saga .
Aunque esta advertencia generalmente aparece debido a una llamada de acción fuera de lugar (por ejemplo, llamar a una acción en render: onClick={action()}
lugar de pasar como lambda: onClick={() => action()}
, si sus componentes parece que has mencionado (simplemente renderizando un div
), entonces esa no es la causa del problema.
Tuve este problema en el pasado y logré resolverlo usando acciones reducidas por lotes .
Es muy útil para casos de uso como el tuyo cuando envías múltiples acciones a la vez y no estás seguro de cuándo se activarán las actualizaciones, con esto, solo habrá un único envío para múltiples acciones. En su caso, parece que la addNotification
posterior está bien, pero la tercera es demasiado, tal vez porque interactúa con la API de historial.
Intentaría hacer algo como (suponiendo que tu setTimeout será reemplazado por una llamada de API por supuesto):
import { batchActions } from ''redux-batched-actions''
export function doLogin (username, password) {
return function (dispatch) {
dispatch(loginRequest())
console.log("Simulated login with", username, password)
setTimeout(() => {
dispatch(batchActions([
loginSuccess(`PLACEHOLDER_TOKEN${Date.now()}`),
addNotification({
children: "Successfully logged in",
type: "accept",
timeout: 2000,
action: "Ok"
}),
push("/domains")
]))
}, 1000)
}
}
Tenga en cuenta que tendrá que descargar el paquete y enableBatching
el enableBatching
su reductor en la creación de su tienda.
En un componente de reacción cuando llamas a setState({...})
, hace que el componente vuelva a renderizar y llame a todos los métodos del ciclo de vida que están involucrados en la re-interpretación del componente y el método de render
es uno de ellos
Cuando está en render
si llama a setState({...})
causará una nueva renderización y el renderizado se setState({...})
una y otra vez, por lo tanto, esto disparará un bucle infinito dentro del javascript que eventualmente llevará a la falla de la aplicación
Por lo tanto, no solo en el render
sino también en cualquier método del ciclo de vida que sea parte del proceso de setState({...})
, no se debe llamar al método setState({...})
.
En su caso, el código podría estar desencadenando una actualización en el estado de reducción mientras el código todavía está en proceso de renderización y, por lo tanto, esto causa que se vuelva a renderizar y la reacción muestre un error.
No hay suficiente información para dar una respuesta determinada. Pero lo que es seguro es que esta advertencia se plantea cuando setState
método de render
dentro de setState
.
La mayoría de las veces sucede cuando llamas las funciones de tu controlador en lugar de pasarlas como accesorios al Component
hijo. Como sucedió aquí o aquí .
Por lo tanto, mi consejo es verificar qué Components
se están procesando en su ruta /domains
y cómo está onChange
controladores de onChange
, onClick
, etc. a ellos y a sus hijos.