Comments (12)
Basically my prop was mutable so as soon as it was mutated, both the previous and next props actually pointed to the same object, having the same values. I switched to using Immutable.js for objects
from react-redux.
I'm not sure what you mean. If you don't mutate Redux state and always return the new state (that's how you should do in Redux), you shouldn't have such problem. Can you show your code?
from react-redux.
I think that might be the issue yes, some object I'm mutating somewhere in the reducer. Just wanted to open an issue ask if that was something you heard about that could be caused by Redux or if it were more an userland error but I assumed it would be the latter yes. Sorry for bothering, will make sure everything is tight and reopen if really I found a bug. :p
from react-redux.
Was indeed an immutability issue, rewriting my reducer fixed the issue.
from react-redux.
Hi ,I had the same problem. Can you tell me the solution or the reason
from react-redux.
+1
from react-redux.
Thanks Anahkiasen. It turns out I too was mutating and returning the object representing the Redux store without realising it.
Redux requires a new object to be returned in the reducer for the shouldComponentUpdate function to show a difference between nextProps and this.props.
I think this.props must directly reference the object returned by the reducer meaning any mutation is already present in the shouldComponentUpdate.
In my case, I was using Object.assign incorrectly in my reducer and returning the original object (instead of creating a new object etc).
In the following example I assumed y was a new object and 'x === y' to return false:
var x = {a: 1};
var y = Object.assign(x, {a: 1});
x === y; //true
To create a new object, you must ensure the first argument in assign is an empty/or new object:
y = Object.assign({}, x, {a: 1});
x === y; //false
from react-redux.
Yep, there's a very good reason why this is in the Redux FAQ: http://redux.js.org/docs/FAQ.html#react-not-rerendering .
Seriously, I think every single time I've seen this question, the cause was mutation in a reducer.
from react-redux.
I am having the same problem, and I've pinned down that it's state being mutated in the reducer, but I can't figure out how it's being mutated. Would anyone mind giving me a hand?
Below is the reducer, and it appears that this is the line that is mutating the state:
newState[action.schedule][action.iterator].shortname = action.answer;
I'm guessing the issue is too deeply nested data. Looking at the console.logs I get LOG2 is false, LOG3 reflects the pre-updated state, but LOG4 reflects the updated state, not pre-updated as it should. LOG5 and beyond reflect the updated state.
Is it possibly something with the spread operator in the top (...state) ? I thought that was it and maybe just had an issue with Babel but I get the same error when I use Object.assign({}, state). I also tried using kolodny/immutability-helper and had the same issue.
Any help would be greatly appreciated!
This is the reducer. It's actually a subreducer, only handling a section of state with multiple insurance schedules (list of autos, list of locations, list of lenders, etc..)
function schedules(state={}, action) {
let newState = {...state};
console.log('LOG1top of reducer - is spread working');
console.log('LOG2newState === state', newState === state);
switch(action.type){
case 'UPDATE_ANSWER_OBJECT':
//receives qref and answer always, schedule and iterator if answer is part of a schedule.
//replaces the answer object (existing or blank)
//make sure when passing actions that you don't overwrite the answer
console.log('LOG3inside sched reducer update answer object. initial state .certificateHolders is', state.certificateHolders[0]);
if (action.schedule !== null) {
if(action.qref === "shortname") {
newState[action.schedule][action.iterator].shortname = action.answer;
console.log('LOG4inside sched reducer update answer object. again showing initial state .certificateHolders is', state.certificateHolders[0]);
console.log('LOG5inside sched reudes update answer object updating newState for shortname. newImmutState is', newState.certificateHolders[0]);
return newState;
} //don't need to build object for the shortname, which isn't part of keys or extra
let theNewAnswer = action.answer;
newState[action.schedule][action.iterator][action.keyInfoOrExtraInfo][action.qref].answer = theNewAnswer;
console.log('LOG6inside sched reducer update answer object. 3rd time showing initial state .certificateHolders is', state.certificateHolders[0]);
console.log('LOG7inside sched reudes update answer object updating newState for shortname. newImmutState is', newState.certificateHolders[0]);
return newState;
}
else {
// console.log('did not update schedules reducer because schedule was something other than null');
return state;
}
break;
If relevant, this is the react component. It receives this.props rather than having it's own separate connect. I would expect this.props to reflect the pre-updated redux state and nextProps post-update, but the console logs in componentWillReceiveProps and shouldComponentUpdate all always reflect the updated props.
import React from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router'
import Question from './Question';
import Card from './Card';
import LineOfCoverageDescription from './LineOfCoverageDescription';
import * as Helpers from '../helpers';
import * as _ from 'lodash';
import componentHelpers from '../componentHelpers';
class TriColumns extends React.Component{
//the Link component is used instaed of normal <a> tag because it uses react-router
//received 'schedule' as props identifing what schedule is being worked on
//when want a subquestion to be a followup question (indented) pass it an extra prop that is followup="followup-question"
constructor(){
super();
this.buildCardStack = this.buildCardStack.bind(this);
this.hydrateInProcessSchedCarousel = this.hydrateInProcessSchedCarousel.bind(this);
this.hydrateQShown = this.hydrateQShown.bind(this);
this.firstNode = this.firstNode.bind(this);
this.nextNode = this.nextNode.bind(this);
this.loopShowQuestions = this.loopShowQuestions.bind(this);
this.shortnameOrCvgDesc = this.shortnameOrCvgDesc.bind(this);
this.showButtons = this.showButtons.bind(this);
this.nextQTrigger = this.nextQTrigger.bind(this);
}
loopShowQuestions(arrayOfQuestions){
const iterator = this.props.scheduleCarousel.schedNode;
return(
arrayOfQuestions.map(
(key, i) => {
return (
<div key={`d${key}`} style={{'width': '100%', 'display': 'table', 'minHeight': '150px'}}>
<Question
{...this.props}
qref={key}
key={i}
schedule={this.props.schedule}
iterator={iterator}
/>
<br />
</div>
)
}
)
)
}
buildCardStack(){
const thisSchedule = this.props.schedules[this.props.schedule];
let stackers = [... thisSchedule];
stackers.splice(this.props.scheduleCarousel.schedNode, 2); //modify stackers by removing card in the active column
if (thisSchedule.length < 2) {
return (<span />) //No card stack because only have 1 card
}
return(
stackers.map(
(node, i) => {
return (
<Card
{...this.props}
schedule={this.props.schedule}
key={i}
iterator={i}
stacked={i}
/>
)
}
)
)
}
hydrateInProcessSchedCarousel(){
// const existingInProcess = this.props.scheduleCarousel.inProcess; //first clear out any remaining questions in case not all were answered
// console.log('rehydrating in process about to begin. first showing pre-rehydrate state of inProcess');
// console.log(existingInProcess);
// existingInProcess.map(
// (qref, i) => {
// this.props.removeFromSchedCarousel(qref, 'inProcess');
// }
// );
this.props.clearSchedCarousel('inProcess')
let schedDefaultCarousel;
if(this.props.schedule === "coverages") {
const targetCvgNode = this.props.schedules.coverages[this.props.scheduleCarousel.schedNode];
const coveragesList = this.props.coverages;
schedDefaultCarousel = coveragesList[targetCvgNode.shortname].initialCarousel;
// schedDefaultCarousel = this.props.coverages[]
}
else {
schedDefaultCarousel = this.props.scheduleCarousel[this.props.schedule];
}
schedDefaultCarousel.map(
(qref, i) => {
this.props.addToSchedCarousel(qref, 'inProcess');
}
);
return [... schedDefaultCarousel]
}
hydrateQShown(carousel){
this.props.clearShownQuestions();
const shownLeft = this.props.questionsShown.left;
if (carousel == shownLeft) {
return ""
}
let shouldShow;
shouldShow = (typeof(carousel[0]) === "undefined") ? false : true;
shouldShow ? this.props.showQuestion(carousel[0]) : "";
}
firstNode(){
//call when first load the component
let nodeSched;
if(this.props.schedule === "coverages") {
nodeSched = 0; //for coverages always start at first and work to end (unlike on other schedules where start on last entered node)
}
else {
nodeSched = Math.max(this.props.schedules[this.props.schedule].length-1, 0) //should be zero if an empty schedule, but if not goes to last one
}
this.props.setSchedNode(nodeSched);
this.hydrateQShown(this.hydrateInProcessSchedCarousel());
}
nextNode(e){
//call when it's time to go to the next question
e.preventDefault();
this.props.removeFromSchedCarousel(this.props.questionsShown.left[0], 'inProcess');
this.props.unshowQuestion(this.props.questionsShown.left[0]);
this.props.createBlankSchedNode(this.props.schedule) //create a new blank node
let nodeSched;
if(this.props.schedule === "coverages") {
nodeSched = this.props.scheduleCarousel.schedNode + 1; //for coverages always start at first then go through until end (unlike on other schedules where start on last entered node)
}
else {
nodeSched = Math.max(this.props.schedules[this.props.schedule].length-1, 0) //identify newly created blank node (last in the array)
}
this.props.setSchedNode(nodeSched); //set nodeReference for questions to newly created blank node (last in the array)
this.hydrateQShown(this.hydrateInProcessSchedCarousel());
}
nextQ(e){
e.preventDefault();
this.props.removeFromSchedCarousel(this.props.questionsShown.left[0], 'inProcess');
this.props.unshowQuestion(this.props.questionsShown.left[0]);
// this.forceUpdate();
this.hydrateQShown(this.props.scheduleCarousel.inProcess);
}
shortnameOrCvgDesc(){
if (this.props.schedule === "coverages") {
return (
<LineOfCoverageDescription
{...this.props}
lref={this.props.schedules.coverages[this.props.scheduleCarousel.schedNode].shortname}
/>
)
}
const shortname = this.props.schedules[this.props.schedule][Math.max(this.props.schedules[this.props.schedule].length-1, 0)].shortname;
return (
<h3>{shortname}</h3>
)
}
componentWillMount(){
const initOrNot = Helpers.isEmpty(this.props.schedules[this.props.schedule]);
initOrNot ? this.props.createBlankSchedNode(this.props.schedule) : "";
this.firstNode();
}
componentWillReceiveProps(nextProps){
console.log('inside componentWillReceiveProps in TriColumns. this.props.schedules.certificateHolders:', this.props.schedules.certificateHolders[0]);
console.log('inside componentWillReceiveProps in TriColumns. nextProps.schedules.certificateHolders:', nextProps.schedules.certificateHolders[0]);
}
shouldComponentUpdate(nextProps){
console.log('inside shouldComponentUpdate in TriColumns. this.props.schedules.certificateHolders:', this.props.schedules.certificateHolders[0]);
console.log('inside shouldComponentUpdate in TriColumns. nextProps.schedules.certificateHolders:', nextProps.schedules.certificateHolders[0]);
return true;
}
showButtons(){
//renders the appropriate buttons based on the current state
const showNextQ = this.props.scheduleCarousel.inProcess.length !== 1 ? true : false;
const showNextNode = !showNextQ;
const showFinished = !showNextQ;
this.nextQTrigger();
return (
<div id="submit-or-clear">
<fieldset>
{showNextQ ? <button type="button" className="btn btn-info" style={{'margin': '10px'}} onClick={(e) => {this.nextQ(e)}} >Next Question</button> : ""}
{showNextNode ? <button type="button" className="btn btn-primary" style={{'margin': '10px'}} onClick={(e) => {this.nextNode(e)}} >Next in {Helpers.camelToClassy(this.props.schedule)}</button> : ""}
{showFinished ? <button type="button" className="btn btn-default" style={{'margin': '10px'}} onClick={(e) => {this.props.changeCenterStage('SchedCoverPage')}} >Done with {Helpers.camelToClassy(this.props.schedule)}</button> : ""}
</fieldset>
</div>
)
}
nextQTrigger(){
//trigger this within showbuttons and see if can trigger re-render on question answer
//change. The problem is can't tie to onblur event directly because that's part of the question\
//component...
const qref = this.props.scheduleCarousel.inProcess[0];
const iterator = this.props.scheduleCarousel.schedNode;
const keyOrExtra = componentHelpers.determineIfKeyInfoOrExtraInfo(qref, this.props.schedule);
const answer = this.props.schedules[this.props.schedule][iterator][keyOrExtra][qref];
// console.log('this.props fromnextQTriggers:', this.props);
// console.log('qref from nextQTrigger', qref);
// console.log('iterator from nextQTrigger', iterator);
// console.log('schedule from nextQTrigger', this.props.schedule);
// console.log('keyOrExtra from nextQTrigger', keyOrExtra);
// console.log('answer from nextQTrigger', answer);
}
render(){
const iterator = this.props.scheduleCarousel.schedNode;
return (
<div id="app" className="container-fluid">
<form>
<div className="row-fluid">
<div id="left-column" className="col-sm-6">
{this.shortnameOrCvgDesc()}
{this.loopShowQuestions(this.props.questionsShown.left)}
{this.showButtons()}
</div>
<div id="active-card-column" className="col-sm-4">
<Card
{...this.props}
schedule={this.props.schedule}
key={this.props.scheduleCarousel.schedNode}
iterator={this.props.scheduleCarousel.schedNode}
/>
</div>
<div id="card-stack-column" className="col-sm-2">
{this.buildCardStack()}
</div>
</div>
</form>
</div>
)
}
};
export default TriColumns;
from react-redux.
@gate5th : yes, that is absolutely doing direct mutation. You're only copying the first level of nesting - you need to copy and update every level of nesting. You also shouldn't copy newState
like that at the start of the reducer - only copy it when you actually hit a case that needs to update.
I'd encourage you to read through http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html , which shows how to do proper nested immutable updates, and discusses several common mistakes, as well as some of the articles listed at http://redux.js.org/docs/recipes/reducers/PrerequisiteConcepts.html . There's also a new FAQ section on immutability: http://redux.js.org/docs/faq/ImmutableData.html .
from react-redux.
Thank you for the super fast response @markerikson! I had not yet seen that new FAQ on immutability and looks extensive so that will be helpful. I didn't realize that the copying had to go further down since I wasn't modifying the middle. I'll read the links in detail and see if I can determine the fix.
This is actually my first coding post: from an etiquette standpoint should I re-post with a solution once I've resolved it? Or is that unnecessary?
from react-redux.
@gate5th : since this was a usage question, it actually should probably have been posted over on Stack Overflow, especially since this is a dead thread. You're lucky I saw the notification and answered :)
No need to re-post anything here. It's a common issue (which is why it's discussed in the docs and FAQ).
Glad you got it working now!
from react-redux.
Related Issues (20)
- Could not find react-redux context value; please ensure the component is wrapped in a <Provider> HOT 4
- Performance of useSyncExternalStore and React transitions HOT 5
- Disconnect from redux HOT 20
- Check why we no longer bail out of subscription checks when `store.getState()` hasn't changed HOT 1
- Rename `noopCheck` to `identityFunctionCheck` HOT 1
- Color visibility of some note HOT 5
- Unable to resolve module react-dom HOT 10
- Jest: Module import syntax error HOT 9
- Unable to resolve peer dependency react-native on [email protected] HOT 1
- TypeError: dispatcher.useSyncExternalStore is not a function HOT 1
- where development or production mode should be specified? HOT 7
- After some time useSelector stops doing re-renders HOT 8
- Provider properties for dev checks to be passed explicitly to new devModeChecks property HOT 7
- Getting Syntax error for react-redux when building project HOT 4
- Connect not working with detox envinronment HOT 1
- Getting issue in middleware of configureStore when update redux-toolkit to 2.0.1 HOT 1
- useDispatch() causing unexpected navigate back to previous screen HOT 3
- Single global store and two Provider occurs an error for microfrontend app HOT 1
- Test fail with provider in react-native HOT 3
- useSelector hook seems to break hook parsing in React Developer Tools HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-redux.