2016-07-23 8 views
1

Warum denkt reagieren, ich bin mutieren Zustand, wenn klar bin ich ein neues Objekt übergeben? Fehle ich etwas? Dies ist mein Reducer zum Hinzufügen einer Frage zu einem Quizobjekt innerhalb einer Reihe von Quizfragen.Redux-Status-Mutation

case types.UPDATE_QUESTION_SUCCESS: { 
    let quizContext = state.filter(quiz => quiz.id === action.quizId); 
    let filteredQuestions = quizContext[0].questions.filter(question => 
    question.questionId !== action.question.questionId 
); 
    filteredQuestions.push(action.question); 
    quizContext[0].questions = filteredQuestions; 
    let quiz = quizContext[0]; 
    return [...state.filter(quiz => quiz.id !== action.quizId), Object.assign({}, quiz)]; 
} 

Fehler:

Invariant Violation: A state mutation was detected between dispatches, in the path `quizes.4.questions.1`. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments) 
+0

Bitte geben Sie die gesamte Reducer-Funktion an. – Timo

Antwort

2

Zunächst einmal keine verschachtelten Zustand haben, da man nur über quizContext[0] kümmern, ist filter Overkill. Just do:

const quiz = state.find(quiz => quiz.id === action.quizId); 

das gleiche Ergebnis zu erzielen.

Ich möchte nicht zu viel Code-Review hier aber noch eine Sache tun. Sie verwenden let, obwohl Sie die ganze Zeit const verwenden sollten. const bedeutet nur, dass Sie die Variable nicht neu zuweisen können, aber Sie können sie noch mutieren.

Dann filtern Sie die Fragen, fügen Sie eine hinzu und fügen Sie sie dann zu dem Quiz zurück, das Sie gerade gefunden haben.

Die quizContext[0].questions = ist das Problem, denn obwohl Ihre Objekte jetzt in einem neuen Array sind, sind sie immer noch die gleichen Objekte, so dass Sie mutieren, wenn Sie sie mutieren.

const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId)); 

So umschreiben die vollständige Sache, die Sie mit etwas so enden würde:

case types.UPDATE_QUESTION_SUCCESS: 
{ 
    const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId)); 
    quiz.questions = quiz.questions.filter(question => 
    question.questionId !== action.question.questionId 
); 
    quiz.questions.push(action.question); 
    return [...state.filter(quiz => quiz.id !== action.quizId), quiz]; 
} 

Aber denken Sie daran, dass die meisten der Zeit, die Sie nicht in Daten filtern Sie wan't Ihre Reduzierer überhaupt. Stattdessen würde ich empfehlen, reselect zu verwenden, damit Sie abgeleitete Daten aus den Daten in Ihrem Geschäft berechnen können.

+0

'Object.assign' macht eine flache Zusammenführung, wenn der Staat eine verschachtelte Struktur hat, könnte dies zu weiteren Mutation führen, da die Referenzen über die erste Ebene tief sind, sind tatsächlich Verweise auf den alten Zustand. – sasidhar

+0

@sasidhar das hätte in diesem Fall keinen Effekt und in Redux, wenn Sie eine tief verschachtelte Struktur haben, würden Sie neue Reduzierungen hinzufügen, die die Tiefe behandeln - alles andere wäre nur schlechte Übung. – mash

0

Sie den Zustand nach Ihrem Minderer mutiert. Wenn Sie eine Filterfunktion verwenden, handelt es sich bei den Objekten, die an den Filter übergeben werden, um Referenzen des ursprünglichen Objekts. Sie tun hier keine deepCopy. Und diese Linie filterQuestions.push(action.question) mutiert tatsächlich den Zustand.

Versuchen Sie stattdessen: let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));

Obwohl es vielleicht da draußen eine elegantere Lösung, das ist, wo Sie falsch gehen werden. Die gefilterten Objekte, die zurückgegeben werden, sind Referenzen in das ursprüngliche Array, aber keine neuen Objekte. Sie mutieren also die ursprünglichen Objekte im Status.

--Mehr clarification--

cloneDeep eine lodash Funktion ist, und wenn Sie Object.assign verwenden, es wird nur eine flache merge tun, aber nicht eine tiefe verschmelzen. Also, wenn der Zustand auf einer tieferen Ebene mutiert ist, sind die Referenzen noch des ursprünglichen Objekts dort durch den ursprünglichen Zustand mutiert

--edit 2--

wie Maische der Linie festgestellt, dass Ihr Zustand mutiert ist quizContext[0].questions = filteredQuestions; und Object.assign verwenden, wenn Sie sicher sind Sie in diesem Fall mehr Reduzierungen sind, um

+0

Der Push selbst mutiert nicht den Zustand!cloneDeep es ist keine Funktion, noch ist es eine gute Übung oder angemessen, wie Sie bereits in diesem Fall bemerkt haben. – mash

+0

@mash Die Push-Funktion ist diejenige, die die Mutation verursacht. Nein? Die Referenz, die es hat, ist in das ursprüngliche Objekt innerhalb des alten Zustands, so dass der Push tatsächlich zum ursprünglichen Objekt drängt, also habe ich nicht verstanden, was du meintest, als du sagtest: "Push selbst verändert den Zustand nicht!" ? – sasidhar

+0

Nein, es ist nicht so, wie der ECMA-262 besagt, dass der Filter das Objekt, auf dem er aufgerufen wird, nicht direkt mutiert, sondern dass das Objekt durch die Aufrufe von Callbackfn mutiert werden kann. Das Hinzufügen zu einem neuen Array fügt also nur ein Element hinzu das neue Array nicht zu 'quizContext [0] .questions'. Die Mutation tritt auf, sobald er sie neu zugewiesen hat. – mash