Предупреждение: setState (...): не может обновляться во время существующего перехода состояния с сокращением/неизменным
Я получаю сообщение об ошибке
Предупреждение: setState (...): не может обновляться во время существующего перехода состояния (например, внутри render
или другого конструктора компонента). Методы визуализации должны быть чистой функцией реквизита и состояния; побочные эффекты конструктора являются анти-шаблонами, но могут быть перенесены на componentWillMount
.
Я нашел причину
const mapStateToProps = (state) => {
return {
notifications: state.get("notifications").get("notifications").toJS()
}
}
Если я не возвращаю уведомления, там он работает. Но почему?
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
UPDATE
При дальнейшей отладке я обнаружил, что вышеизложенное не может быть основной причиной в конце концов, у меня могут быть уведомления, но мне нужно удалить dispatch(push("/domains"))
мое перенаправление.
Вот как я вхожу в систему:
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)
}
}
Я обнаружил, что отправка вызывает предупреждение, но почему? На моей доменной странице сейчас ничего нет:
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>
)
}
}
ОБНОВЛЕНИЕ 2
Мой App.jsx
. <Notifications />
- это то, что отображает уведомления
<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>
Ответы
Ответ 1
Ваш dispatch(push('/domains'))
поставляется вместе с другими диспетчерами, которые устанавливают состояние подключенного компонента (предположительно, что касается уведомлений), который повторно монтируется/размонтируется после вступления в действие push
.
Как обходное решение и доказательство концепции, попробуйте отложить вызов dispatch(push('/domains'))
с помощью вложенной нулевой секунды setTimeout
. Это должно выполнить push
после завершения любого из других действий (например, рендеринга):
setTimeout(() => dispatch(push('/domains')), 0)
Если это сработает, вам может потребоваться пересмотреть структуру вашего компонента. Я полагаю, что Notifications
- это компонент, который вы хотите установить один раз и сохранить его там на протяжении всего срока службы приложения. Попытайтесь не пересобирать его, поставив его выше в дереве компонентов и сделав его PureComponent
(здесь docs). Кроме того, если сложность вашего приложения увеличивается, вам следует рассмотреть возможность использования библиотеки для обработки асинхронных функций, таких как redux-saga.
Несмотря на то, что это предупреждение обычно появляется из-за неправильного вызова действия (например, вызов действия для рендера: onClick={action()}
вместо передачи как лямбда: onClick={() => action()}
), если ваши компоненты выглядят так, как вы упомянули (просто рендеринг a div
), то это не является причиной проблемы.
Ответ 2
У меня была эта проблема в прошлом, и мне удалось ее решить, используя redux-batched-actions.
Это очень полезно для случаев использования, например, при отправке кратных действий сразу, и вы не уверены в том, когда будут активированы обновления, при этом будет только одна отправка нескольких действий. В вашем случае кажется, что последующий addNotification
хорош, но третий слишком много, может быть, потому, что он взаимодействует с историей api.
Я бы попытался сделать что-то вроде (если ваш setTimeout будет заменен вызовом api, конечно):
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)
}
}
Обратите внимание, что вам нужно будет загрузить пакет и enableBatching
ваш редуктор в создании вашего магазина.
Ответ 3
Причина, по которой это происходит, - это когда вы вызываете doLogin
, если вы вызываете ее из constructor
. Если это так, попробуйте переместить его на componentWillMount
, хотя вы должны вызывать этот метод с помощью кнопки или вводить хит в форме входа.
Это описано в конструктор не должен мутировать. Если это не корень проблемы, вы не прокомментировали каждую строку в doLogin, чтобы знать точно, какая строка дает проблему состояния, я предполагаю, что это будет либо push
, либо addNotification
Ответ 4
Недостаточно информации, чтобы дать определенный ответ. Но точно, что это предупреждение возникает при попытке setState
внутри метода render
.
Чаще всего это происходит, когда вы вызываете свои функции обработчика вместо передачи их в качестве реквизита для дочернего Component
. Например, здесь здесь или здесь.
Итак, мой совет - дважды проверить, какие Components
отображаются на вашем трассе /domains
, и как вы передаете им теги onChange
, onClick
и т.д. и т.д.
Ответ 5
В реагирующем компоненте при вызове setState({...})
он заставляет компонент повторно отображать и вызывать все методы жизненного цикла, которые участвуют в повторном рендеринге компонента, а метод render
- один из них
Если в render
, если вы вызываете setState({...})
, это вызовет повторную визуализацию, и рендер будет вызываться снова и снова, поэтому это вызовет бесконечный цикл внутри javascript, что в конечном итоге приведет к сбою приложения
Следовательно, не следует вызывать не только в render
, но и в любом методе жизненного цикла, который является частью процесса повторного рендеринга setState({...})
.
В вашем случае код может запускать обновление в состоянии redux, пока код все еще отображается, и, следовательно, это вызывает повторную визуализацию и реакцию показывает ошибку