The scenario I'm facing is I have an array of jobs stored as Redux state. I have a container which accesses the data with Redux connect and uses parameter from this.props.params to then find the appropriate Job to then hand down to it's children as a prop.
Within the Child Component the Job triggers an action which updates the job and then is merged and updates the jobs in the store. This seems to be working fine but when the container re-renders the child does not. I can see that is has something to do with the fact that I don't store job as props or state.
My question is what is the best (Redux)way to handle this?
So if I change JobShow to include jobs={jobs} it re-renders as jobs has updated. This seems to indicate that state is not mutating, I also can confirm that the container itself re-renders and even re-runs renderView() but doesn't re-render JobShow.
Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import JobShow from './job_show';
class JobContainer extends Component {
constructor(props){
super(props);
}
renderView(job, customer){
let viewParam = this.props.params.view;
switch(viewParam){
case "edit": return <JobEdit customer={customer} job={job}/>
case "notes": return <JobShow customer={customer} activeTab={"notes"} job={job}/>
case "quote": return <JobShow customer={customer} activeTab={"quote"} job={job}/>
case "invoice": return <JobShow customer={customer} activeTab={"invoice"} job={job}/>
default: return <JobShow customer={customer} activeTab={"notes"} job={job}/>
}
}
render() {
let { jobs, customers } = this.props;
if (jobs.length < 1 || customers.length < 1){
return <div>Loading...</div>;
}
let job = jobs.find(jobbie => jobbie.displayId == this.props.params.jobId);
let customer = customers.find(customer => customer._id.$oid == job.customers[0]);
return (
<div className="rhs">
{this.renderView(job, customer)}
</div>
);
}
}
JobContainer.contextTypes = {
router: React.PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
jobs: state.jobs.all,
customers: state.customers.all
};
}
export default connect(mapStateToProps, actions )(JobContainer);
Reducer Snippet:
import update from 'react-addons-update';
import _ from 'lodash';
export default function(state= INITIAL_STATE, action) {
switch (action.type) {
case FETCH_ALL_JOBS:
return { ...state, all: action.payload, fetched: true };
case UPDATE_JOB_STATUS:
let newStatusdata = action.payload;
let jobToUpdate = state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId);
jobToUpdate.status = newStatusdata.newStatus;
jobToUpdate.modifiedAt = newStatusdata.timeModified;
let updatedJobIndex = _.indexOf(state.all, state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId));
let updatedState = update(state.all, {$merge: {[updatedJobIndex]: jobToUpdate}});
return { ...state, all: updatedState}
jobToUpdate.status = ....
. Accidental mutation is almost always the reason for components not re-rendering. See redux.js.org/docs/FAQ.html#react-not-rerendering. Also, what do the jobs and your initial state look like? – markeriksonjobToUpdate
lines in the reducer snippet are directly mutating that job object, rather than making a copy, only modifying the copy, and returning that copy. So yes, if you're trying to pass that job object itself to a child component as a prop, the reference itself hasn't changed and the child component will not update. – markerikson