GithubHelp home page GithubHelp logo

Comments (12)

Anahkiasen avatar Anahkiasen commented on April 27, 2024 6

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.

gaearon avatar gaearon commented on April 27, 2024 1

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.

Anahkiasen avatar Anahkiasen commented on April 27, 2024

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.

Anahkiasen avatar Anahkiasen commented on April 27, 2024

Was indeed an immutability issue, rewriting my reducer fixed the issue.

from react-redux.

aDeadFish avatar aDeadFish commented on April 27, 2024

Hi ,I had the same problem. Can you tell me the solution or the reason

from react-redux.

chrisgwgreen avatar chrisgwgreen commented on April 27, 2024

+1

from react-redux.

chrisgwgreen avatar chrisgwgreen commented on April 27, 2024

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.

markerikson avatar markerikson commented on April 27, 2024

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.

gate5th avatar gate5th commented on April 27, 2024

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.

markerikson avatar markerikson commented on April 27, 2024

@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.

gate5th avatar gate5th commented on April 27, 2024

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.

markerikson avatar markerikson commented on April 27, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.