【问题标题】:Cannot update during an existing state transition error在现有状态转换错误期间无法更新
【发布时间】:2019-04-05 08:42:53
【问题描述】:

编辑:我解决了我的问题,它现在对我有用 - 还编辑了我的代码以反映新的变化。

我收到此错误,但我不确定此错误的原因。

因为是公司的材料所以无法展示代码,所以我会尽力描述它:

App.js:

`class App extends React.Component {
    constructor(props) {
        super(props);
    }

    render () {
        return (
            <div>
                <Header />
                <RouteList />
                <Footer />
            </div>
        )
    }
}`

我的&lt;RouteList /&gt; 是一个无状态函数,它返回网络应用程序的所有路由。

Header.js:

class Header extends React.Component {
    constructor (props) {
        super(props);
        this.changeHeader = this.changeHeader.bind(this);
    }

    changeHeader(headerType) {
        this.props.actions.changeHeader(headerType)
    }

    GetHeader() {
        // if-else statement which will return a different sub header class
        const HeaderType = this.props.renderHeader.headerType
        if (headerType == 'abc') {
            <aHeader changeHeader={this.changeHeader} />
        } [...] {
            // Final else block return something
        }
    }

    render () {
        return (
            <div>{this.GetHeader()}</div>
        )
    }
}

function mapStateToProps(state, ownProps) {
    return { renderHeader: state.renderHeader};
}

function mapDispatchToProps(dispatch) {
    return { actions: bindActionCreators(headerActions, dispatch) };
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));

this.props.action.changeHeader(headerType) 是一个 if-else 语句,根据 headerType 的值,将触发不同的操作。

state.renderHeader 在我的 rootReducer 中声明。

我将changerHeader() 传递给无状态的单个 header.js(即 aHeader.js、bHeader.js ...)。单击导航链接时,它将调用该方法并将页面路由到另一个 UI。这就是我将方法嵌入导航链接的方式:onClick={changeHeader(input')}

rootReducer.js

const rootReducer = combineReducers({renderHeader});
export default rootReducer;

renderHeader就是renderHeaderReducer。

headerAction.js

export function changeHeader(headerType) {
    if (headerType == "abc") {
        return {type: type, headerType: "abc"}
    } [...] {
        // something default
    }
}

renderHeaderReducer.js

export default function renderHeaderReducer(state = initialState, action) {
switch(action.type) {
    case "abc":
        return (Object.assign({}, ...state, {headerType: action.headerType}));
    [...];
    default:
        return state;
}

}

此时单击链接时,Web 浏览器应刷新,将 Header 留在原处但修改部分。但是我的网站进入了一个无限循环,错误是:

错误:在现有状态转换期间无法更新(例如在渲染或其他组件的构造函数中)。渲染方法应该是 props 和 state 的纯函数;构造函数副作用是一种反模式,但可以移动到 componentWillMount。

当我执行 console.log 以查看发生了什么时,它似乎正在循环我定义的所有各种选项,这些选项将呈现 Header.js

事实证明,主要问题是当我调用我的 onClick 方法时。导致我的代码出错的无限循环是 onClick 函数触发的结果,即使没有被点击。

原文:onClick={this.changeHeader('abc')} 新:onClick={() =&gt; changeHeader('abc')}

请参阅此post 以获得解释。

谢谢。

【问题讨论】:

  • 你说 state.renderHeader 是在你的 rootReducer 中声明的;但是,您不应该从代码中调用 reducer 函数。相反,您应该调用操作,这反过来会触发适当的 reducer 函数来修改您的状态。
  • @sme 我调用了这个动作this.props.actions.changeHeader(headerType)
  • 您是否在任何地方调用renderHeader 函数?我仍在试图弄清楚这是否对您的代码有影响。但同样,如果它是一个 reducer 函数,它不应该在你的 mapStateToProps() 函数中。
  • @sme,我没有在其他任何地方调用我的 renderHeader 函数。我认为 mapStateToProps() 函数应该将减速器映射到道具?
  • mapStateToProps 应该只用于从你的 redux 存储(状态)传递属性,或者通过ownProps 直接传递给组件的属性。动作创建者应该在 mapDispatchToProps() 中,据我所知,这在您的代码中是正确的。一旦调用了动作创建函数,它将创建动作,然后根据动作包含的type 属性自动调用适当的reducer。你不需要手动调用任何reducer,也不需要将它们传递给任何组件

标签: reactjs redux react-redux react-router-v4


【解决方案1】:

一些伪代码的时间:)

据我了解,您有一个 Header 组件,它连接到一个 rootReducer 组件,该组件包含您所在的路由器链接的标题。

我的应用程序中有一些类似的代码,我们使用单个组件调度操作来更新 rootReducer 标头。标头仅使用 redux 侦听更新并重新呈现自身。

class Header extends React.Component {
     // render the header 
     render() {...}
 }
 const mapStateToProps = (state) => {
     return {
        header: state.rootReducer.header
     }
 }
 export default withRouter(connect(mapStateToProps, {})(Header));

组件

 class MySpecialRouteComponent extends React.Component {
     componentDidMount() {
         this.props.changeHeader("Special Component")
     }
     render() {...}
 }
 const mapStateToProps = (state) => {
     return {
       ...whatever 
     }
 }
 export default withRouter(connect(mapStateToProps, {changeHeader})(MySpecialRouteComponent));

你不应该让 render 在 React 中执行 setState!

【讨论】:

  • 你能解释一下为什么把 {changeHeader} 放在 mapDispatchToProps 的位置吗?
  • 当前显示的标头应该保存在 Redux 存储中。您应该有一个动作创建者changeHeader(headerType),它将创建一个动作来更改商店中的标题类型。然后,reducer 将使用您传递给操作创建者的headerType 更新存储。
  • 我在代码中添加了更多编辑,显示了操作和减速器
  • @sme 是对的。 changeHeader 是一个动作创建者,它调度和更新根减速器中的状态。即使在更新了更多代码 AKJ 之后,我觉得我的方法至少在这种情况下更适合渲染标题。没有苛刻的感觉:)
  • @dixitk13 无意冒犯,我真的很想学习成为一名更好的编码员,您和 sme 都提供了惊人的帮助。你能详细说明你的代码吗?我想看看你的结构如何。我现在正在尝试 sme 的,看看它是否适合我。为什么我不应该让 render 做 setState?有什么优点和缺点?
【解决方案2】:

我将展示如何设置一切来处理这种情况。

redux/header-actions.js(从您的组件中调用这些动作创建者):

export const changeHeader = (headerType) => {
    return {
        type: 'CHANGE_HEADER',
        payload: {
            headerType: headerType
        }
    }
}

redux/header-reducers.js(注意:这将在您调用操作时处理):

const INITIAL_STATE = {
    headerType: 'header-a'
};

export default function(state = INITIAL_STATE, action) {
    switch (action.type) {
        case 'CHANGE_HEADER':
            return changeHeader(state, action.payload);
        default:
            return state;
    }
}

const changeHeader = (state, payload) => {
    // this is where your app will update the state, to indicate
    // which header should be displayed:
    return {
        ...state,
        headerType: payload.headerType
    }
}

redux/index.js

import headerReducers from './reducers/header-reducers';
import { combineReducers } from 'redux';

const allReducers = combineReducers({
    header: headerReducers
});

export default allReducers;

现在您可以像这样设置 header.js 组件:

import { changeHeader } from '../redux/header-actions';

class Header extends Component {

    render() {
        return (
            <div>{this.renderHeader()}</div>
        );
    }

    renderHeader() {
        if (this.props.headerType === 'header-a')
            return <aHeader changeHeader={this.props.changeHeader} />
        else
            return <bHeader changeHeader={this.props.changeHeader} />;
    }

}

function mapStateToProps(store, ownProps) {
    return {
        headerType: store.header.headerType
    };
}

function mapDispatchToProps(dispatch) {
    return bindActionCreators({
        changeHeader: changeHeader
    },
    dispatch);
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));

然后在例如 aHeader.js 中:

class aHeader {

    constructor() {
        super();
        this.changeHeader = this.changeHeader.bind(this);
    }

    render() {
        return <div onClick={this.changeHeader}>header a</div>;
    }

    changeHeader() {
        this.props.changeHeader('header-b'); // call the action creator here
    }
}

【讨论】:

  • 顺便说一句,allReducers 中有一个错字,它必须是 headerReducers 而不是 header-reducer。同样对于 OP 遵循的这种特定方法,如果明天他在标题中有另一个道具,假设每次都会在他的路线中更新,那么他的标题不会经历几次重新渲染吗?因为每当一个渲染调用发生时,一个额外的渲染调用发生在头部组件中,因为动作被调度了?
  • 我有一个问题,你为什么不使用 Object.assign({}, payload.headerType)?我是 ReactJS 的新手,我想了解是否总是需要使用 Object.assign()。我在 Pluralsight 上遵循的教程说我应该这样做。谢谢大佬
  • @dixitk13 这不是错字,在 header-reducer.js 中我正在导出一个默认的未命名函数,因此您可以在导入时随意调用它。
  • 我的意思是在 redux/index.js 中。既然你导入了headerReducers,你不应该使用header: headerReducers吗?
  • @AKJ 如果要修改存储/状态中的对象,则应使用 object.assign。但这里没有必要,因为我完全用新值覆盖了旧的headerType 值,而不是直接操作状态。
猜你喜欢
  • 2023-03-13
  • 2016-03-31
  • 1970-01-01
  • 2017-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-29
相关资源
最近更新 更多