Предупреждение: 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, пока код все еще отображается, и, следовательно, это вызывает повторную визуализацию и реакцию показывает ошибку