Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Successful dispatch does not cause re-render even though state has been changed

Despite a successful update of my state (checked through console.log and redux devtools) I cannot get my views to re-render

You can view the code at https://github.com/attendanceproject/djattendance/tree/attendance-redux/ap/static/react-gulp/app

Most of the code is in the scripts folder, the most important parts pertaining to my problem are below. I'm trying to re-render every time the previous or next button is pressed in the WeekBar component so that the date in there updates accordingly.

container code

class Attendance extends Component {
  render() {
    const { dispatch, trainee, date, events, rolls, slips, selectedEvents } = this.props
    console.log('this.props', this.props)
    return (
      <div>
        <div>
        <Trainee trainee={trainee} />
        <WeekBar  
          date={date} 
          onPrevClick={date => dispatch(prevWeek(date))}
          onNextClick={date => dispatch(nextWeek(date))}/>
        </div>
        <hr />
      </div>
    )
  }
}

Attendance.propTypes = {
  trainee: PropTypes.shape({
    name: PropTypes.string,
    id: PropTypes.number,
    active: PropTypes.bool
  }).isRequired,
  date: PropTypes.object.isRequired,
  events: PropTypes.array.isRequired,
  rolls: PropTypes.array.isRequired,
  slips: PropTypes.array.isRequired,
  selectedEvents: PropTypes.array
}

function select(state) {
  return {
    trainee: state.trainee,
    date: state.date,
    events: state.events,
    rolls: state.rolls,
    slips: state.slips,
    selectedEvents: state.selectedEvents,
  }
}

export default connect(select)(Attendance)

component code

export default class WeekBar extends Component {
  render() {
    console.log("render props", this.props)
    // var startdate = this.props.date.weekday(0).format('M/D/YY');
    // var enddate = this.props.date.weekday(6).format('M/D/YY');
    return (
      <div className="btn-toolbar" role="toolbar">
        <div className="controls btn-group">
          <button className="btn btn-info"><span className="glyphicon glyphicon-calendar"></span></button>
        </div>
        <div className="controls btn-group">
          <button className="btn btn-default clndr-previous-button" onClick={(e) => this.handlePrev(e)}>Prev</button>
          <div className="daterange btn btn-default disabled">
            {this.props.date.weekday(0).format('M/D/YY')} to {this.props.date.weekday(6).format('M/D/YY')}
          </div>
          <button className="btn btn-default clndr-next-button" onClick={(e) => this.handleNext(e)}>Next</button>
        </div>
      </div>
    );
  }

  handlePrev(e) {
    console.log("hello!", e)
    this.props.onPrevClick(this.props.date)
  }

  handleNext(e) {
    this.props.onNextClick(this.props.date)
  }
}

WeekBar.propTypes = {
  onPrevClick: PropTypes.func.isRequired,
  onNextClick: PropTypes.func.isRequired,
  date: PropTypes.object.isRequired,
}

reducer code

var date = moment()
function handleWeek(state = date, action) {
  switch (action.type) {
    case PREV_WEEK:
      console.log('PREV_WEEK')
      return Object.assign({}, state, {
        date: action.date.add(-7, 'd')
      })
    case NEXT_WEEK:
      return Object.assign({}, state, {
        date: action.date.add(7, 'd')
      })
    default:
      return state
  }
}

export default handleWeek
like image 903
davidhtien Avatar asked Dec 20 '15 00:12

davidhtien


People also ask

Does changing state cause a Rerender?

A second or subsequent render to update the state is called as re-rendering. React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.

Does dispatching an action cause Rerender?

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.

How do you stop a component from rendering even after state has been updated?

By default, when we call this method, the component re-renders once it receives new props, even though the props have not changed. To prevent the render method from being called, set the return to false, which cancels the render. This method gets called before the component gets rendered.

Does Redux state change cause re-render?

React-redux component does not rerender on store state change.


1 Answers

I haven’t taken a close look but you seem to be using Moment.js dates as part of your model. Specifically, onNextClick dispatches an action: dispatch(nextWeek(date)).

The action creator just passes the Moment.js date along:

export function nextWeek(date) {
    return {type: NEXT_WEEK, date}
}

Finally, the reducer mutates the date object by calling add:

return Object.assign({}, state, {
  date: action.date.add(7, 'd') // wrong! it's mutating action.date
})

From Moment.js add documentation:

Mutates the original moment by adding time.

However we emphasize in Redux documentation that reducers must be pure, and that the state must never be mutated, or React Redux won’t see the changes. This is what enables Redux to be efficient because it only re-renders what it knows to have changed.

The solution I suggest is to stop using Moment.js as a part of your state. Use regular JavaScript Date objects, make sure to never mutate them, and only use Moment.js inside your components’ render methods.

Finally, passing data in action derived from the current state is an anti-pattern. Your action currently looks like this:

{type: NEXT_WEEK, date}

But this is too much information! The reducer already knows the current date from the state so there is no need to pass it.

Instead, you can fire an action without the date:

{type: NEXT_WEEK}

and teach your reducer to use the current date when calculating a new one.

Assuming you changed your code to keep a Date object in the state, you can use vanilla JS Date API (it’s not very nice though because Dates are also mutable):

// create a copy of the date object
let newDate = new Date(state.date.getTime());

// mutating here is fine: we mutate a new object
newDate.setDate(newDate.getDate() + 7);

return Object.assign({}, state, {
  date: newDate
})

Alternatively you can use a wonderful new library called date-fns which embraces immutability:

import addDays from 'date-fns/add_days';

// ...

return Object.assign({}, state, {
  date: addDays(state.date, 7) // non mutating! :D
})

If you take care to never mutate the state or the action and always create new objects when data changes, React Redux will correctly update the React component in response to those changes.

like image 148
Dan Abramov Avatar answered Sep 20 '22 08:09

Dan Abramov