在调度之间检测到Redux突变

时间:2017-02-27 00:38:40

标签: reactjs redux react-redux

我在使用当前正在进行的反应还原方面遇到了一些麻烦。我对Redux来说相对较新,所以也许我在这里错过了一个简单的概念,但我想要做的就是为纸牌游戏构建一个甲板构建应用程序,我希望能够保存任何时候用户在他们的套牌中添加或删除卡片。

但是,只要我点击添加或删除,我就会在尝试发送更新操作时收到以下错误消息。

错误消息如下:

Uncaught Error: A state mutation was detected between dispatches, in the path `decks.0.cards.mainboard.0.quantity`. This may cause incorrect behavior.

我的容器组件

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import DeckMobileDisplay from './DeckMobileDisplay';
import * as deckActions from '../../actions/deckActions';

export class DeckEditorContainer extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            deck: Object.assign({}, this.props.deck)
        }

        this.addCard = this.addCard.bind(this);
        this.removeCard = this.removeCard.bind(this);
    }

    addCard(board, cardName) {
        let deck = this.state.deck;
        let cards = this.state.deck.cards;

        cards[board].forEach(i => {
             if(i.name === cardName)
                i.quantity += 1;
        });

        const update = Object.assign(deck, cards);

        this.props.deckActions.updateDeck(update).then(deck => {
            console.log(deck);
        })
        .catch(err => {
            console.log(err);
        });
    }

    removeCard(board, cardName) {
        let deck = this.state.deck;
        let cards = this.state.deck.cards;

        cards[board].forEach(i => {
             if(i.name === cardName) {
                 if (i.quantity === 1) {
                     cards[board].splice(cards[board].indexOf(i), 1);
                 }
                 else {
                     i.quantity -= 1;
                 }

             }
        });

        const update = Object.assign(deck, cards);

        this.props.deckActions.updateDeck(update).then(deck => {
            console.log(deck);
        })
        .catch(err => {
            console.log(err);
        });
    }

    render() {
        const deck = Object.assign({}, this.props.deck);

        return (
            <div className="editor-container">
                <DeckMobileDisplay
                    deck={deck}
                    addCard={this.addCard}
                    removeCard={this.removeCard}
                    />
            </div>
        );
    }
}

DeckEditorContainer.PropTypes = {
    deck: PropTypes.object
};

function getDeckById(decks, id) {
    const deck = decks.filter(deck => deck.id == id);

    if (deck.length) return deck[0];
    return null;
}

function mapStateToProps(state, ownProps) {
    const deckId = ownProps.params.id;
    let deck = {
        id: '',
        userId: '',
        cards: []
    }

    if (state.decks.length > 0) {
        deck = getDeckById(state.decks, deckId);
    }

    return {
        deck: deck
    };
}

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

export default connect(mapStateToProps, mapDispatchToProps)(DeckEditorContainer);

DeckMobileDisplay的组件

import React, {PropTypes} from 'react';
import TabContainer from '../common/Tabs/TabContainer';
import Tab from '../common/Tabs/Tab';
import CardSearchContainer from '../CardSearch/CardSearchContainer';
import DeckList from './DeckList.js';

class DeckMobileDisplay extends React.Component {
    render() {
        return (
            <TabContainer>
                <Tab title="DeckList">
                    <DeckList
                        deck={this.props.deck}
                        addCard={this.props.addCard}
                        removeCard={this.props.removeCard}
                    />
                </Tab>
                <Tab title="Search">
                    <CardSearchContainer
                        addCard={this.props.addCard}
                        removeCard={this.props.removeCard}
                        />
                </Tab>
                <Tab title="Stats">
                    <p>stats coming soon...</p>
                </Tab>
            </TabContainer>
        );
    }
}

DeckMobileDisplay.propTypes = {
    deck: PropTypes.object.isRequired,
    addCard: PropTypes.func.isRequired,
    removeCard: PropTypes.func.isRequired
}

export default DeckMobileDisplay;

相关操作

export function createDeck(deck) {
    return dispatch => {
        dispatch(beginAjaxCall());

        const config = {
            method: 'POST',
            headers: { 'Content-Type' : 'application/json' },
            body : JSON.stringify({deck: deck})
        };

        return fetch(`http://localhost:3000/users/${deck.userId}/decks`, config)
            .then(res => res.json().then(deck => ({deck, res})))
            .then(({res, deck}) => {
                if (res.status >= 200 && res.status < 300) {
                    dispatch(createDeckSuccess(deck.deck));
                }
                else
                    dispatch(createDeckFailure(deck));
            })
            .catch(err => {
                console.log(err);
                dispatch(ajaxCallError(err));
            });
    };
}

export function updateDeck(deck) {
    return dispatch => {
        dispatch(beginAjaxCall());

        const body = JSON.stringify({deck: deck});

        const config = {
            method: 'PUT',
            headers : { 'Content-Type' : 'application/json' },
            body: body
        };

        return fetch(`http://localhost:3000/decks/${deck.id}`, config)
            .then(res => res.json().then(deck => ({deck, res})))
            .then(({res, deck}) => {
                if (res.status >= 200 && res.status < 300) {
                    dispatch(updateDeckSuccess(deck.deck));
                }
                    dispatch(ajaxCallError(err));
            })
            .catch(err => {
                console.log(err);
                dispatch(ajaxCallError(err));
            });
    };
}

export function updateDeckSuccess(deck) {
    return {
        type: types.UPDATE_DECK_SUCCESS,
        deck
    };
}

我的甲板减速机

import * as types from '../actions/actionTypes';
import initialState from './initialState';

export default function deckReducer(state = initialState.decks, action) {
    switch (action.type) {
        case types.LOAD_USERS_DECKS_SUCCESS:
            return action.decks;

        case types.CREATE_DECK_SUCCESS:
            return [
                ...state,
                Object.assign({}, action.deck)
            ]

        case types.UPDATE_DECK_SUCCESS:
            return [
                ...state.filter(deck => deck.id !== action.deck.id),
                Object.assign({}, action.deck)
            ]

        default:
            return state;
    }
}

如果你需要查看更多应用程序,那么repo就在这里:

https://github.com/dgravelle/magic-redux

感谢任何形式的帮助,谢谢!

1 个答案:

答案 0 :(得分:0)

您的问题是由于您手动修改组件的状态而引起的。 Redux的一个原则是:

  

状态为只读

     

改变状态的唯一方法是发出一个动作,一个对象   描述发生的事情。

     

这确保了视图和网络回调都不会   永远写到国家。相反,他们表达了一种意图   改变国家。因为所有变化都是集中的并且发生   一个接一个严格的命令,没有微妙的竞争条件   提防。由于动作只是普通对象,因此可以记录它们,   序列化,存储,然后重放以进行调试或测试   目的。

在方法removeCard中,您正在修改状态:

removeCard(board, cardName) {
    let deck = this.state.deck;
    //This is just a reference, not a clone
    let cards = this.state.deck.cards;

    cards[board].forEach(i => {
         if(i.name === cardName) {
             if (i.quantity === 1) {
                 //Here you are modifying cards, which is a pointer to this.state.deck.cards
                 cards[board].splice(cards[board].indexOf(i), 1);
             }
             else {
                 //Here you are modifying cards, which is a pointer to this.state.deck.cards
                 i.quantity -= 1;
             }
         }
    });
    //... more stuff
}

您可能缺少的一个概念是this.state.deck.cards是指向数组内存位置的引用/指针。如果你想改变它,你需要克隆它。

一种解决方案可能是克隆原始数组:

removeCard(board, cardName) {
    let deck = this.state.deck;
    //Here you are cloning the original array, so cards references to a totally different memory position
    let cards = Object.assign({}, this.state.deck.cards);

    cards[board].forEach(i => {
         if(i.name === cardName) {
             if (i.quantity === 1) {
                 cards[board].splice(cards[board].indexOf(i), 1);
             }
             else {
                 i.quantity -= 1;
             }

         }
    });
    //... more stuff
}

希望它对你有所帮助。