When I change the data for a budget object in my redux store the components that are responsible for rendering that object aren't re-rendering the component when the data is changed.
I have a <Budget /> component that renders a budget object contained in an array of budget objects that are part of my state/store. My store has the following shape:
departmentBudgets : {
isFetching : false,
budgets : [ {
department : 'String',
owner : {
name : 'String',
email : 'String',
title : 'String'
},
budget : 'Number'
} ]
}
I would like the <Budget /> component to re-render and update the display of the budget object when one of its properties are edited. The functionality I'm working on is allowing the user to edit and update the budget property. My redux reducer looks like the following:
import * as types from '../constants/actionTypes';
import union from 'lodash/array/union';
import uniq from 'lodash/array/uniq';
const initialState = {
isFetching : false,
budgets : []
};
export default function budgets( state = initialState, action = {} ) {
const { type, payload, meta } = action;
switch ( type ) {
case types.FETCH_CURRENT_BUDGETS:
return Object.assign({}, state, {
isFetching : true
});
case types.RECEIVE_CURRENT_BUDGETS:
return Object.assign({}, state, {
isFetching : false,
budgets : uniq(union(state.budgets, payload), '_id')
});
case types.UPDATE_DEPARTMENT_BUDGET:
const changedIndex = state.budgets.findIndex(( budget ) => {
return budget._id === payload._id;
});
const newBudgets = [ ...state.budgets.slice(0, changedIndex),
Object.assign({}, state.budgets[ changedIndex ], {
budget : payload.budget
}),
...state.budgets.slice(changedIndex + 1) ];
return Object.assign({}, state, {
budgets : newBudgets
});
default:
return state;
}
}
Am I handling the update correctly in the reducer above? I believe that this isn't mutating the state object, but maybe not?
My react component tree looks like the following to render this data:
DepartmentBudgetsContainer
DepartmentBudgets
Budget
The container element grabs the current data from the database and connects it to the store with react-redux and looks like the following:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { fetchBudgets, updateBudget } from '../../actions/departmentBudget.actions';
import DepartmentBudgets from '../../components/administration/department_budgets/DepartmentBudgets';
const displayName = 'DepartmentBudgetContainer';
const propTypes = {
budgets : PropTypes.object
};
class DepartmentBudgetContainer extends Component {
constructor( props, context ) {
super(props, context);
this.budgetDidchange = this.budgetDidchange.bind(this);
}
componentDidMount() {
let { dispatch } = this.props;
dispatch(fetchBudgets());
}
budgetDidchange( id, budget ) {
let { dispatch } = this.props;
const newBudget = {
_id : id,
budget : budget
};
dispatch(updateBudget(newBudget));
}
render() {
const { budgets } = this.props;
return (
<DepartmentBudgets updateBudget={this.budgetDidchange} budgets={budgets.budgets} />
);
}
}
DepartmentBudgetContainer.displayName = displayName;
DepartmentBudgetContainer.propTypes = propTypes;
function mapStateToProps( state ) {
return {
budgets : state.departmentBudgets.budgets
}
}
export default connect(mapStateToProps)(DepartmentBudgetContainer);
And then the DepartmentBudgets component is responsible for creating each of the Budget components and looks like the following:
import React, { Component, PropTypes } from 'react';
import TableComps from '../../../../node_modules/material-ui/lib/table';
import Budget from './Budget';
const Table = TableComps.Table;
const TableHeader = TableComps.TableHeader;
const TableRow = TableComps.TableRow;
const TableHeaderColumn = TableComps.TableHeaderColumn;
const TableBody = TableComps.TableBody;
const TableRowColumn = TableComps.TableRowColumn;
const displayName = 'DepartmentBudgets';
const propTypes = {
budgets : PropTypes.arrayOf(PropTypes.object).isRequired,
updateBudget : PropTypes.func.isRequired
};
export default class DepartmentBudgets extends Component {
constructor( props ) {
super(props);
}
render() {
const { budgets, updateBudget } = this.props;
const budgetsElements = budgets.map(( budget ) => {
return <Budget updateBudget={updateBudget} key={budget._id} budget={budget} />
});
return (
<Table>
<TableHeader adjustForCheckbox={false}
displaySelectAll={false}>
<TableRow onCellClick={this.handleCellClick}>
<TableHeaderColumn>Department</TableHeaderColumn>
<TableHeaderColumn>Budget</TableHeaderColumn>
<TableHeaderColumn>Owner</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody stripedRows={false}>
{budgetsElements}
</TableBody>
</Table>
);
}
}
DepartmentBudgets.displayName = displayName;
DepartmentBudgets.propTypes = propTypes;
Finally, the Budget component is responsible for rendering the actual budget. I also wanted to include the ability to edit the budget in-line, which functionality is included in this component. However, when I edit the budget, the component is not re-rendering:
import React, { Component, PropTypes } from 'react';
import Table from '../../../../node_modules/material-ui/lib/table';
import numeral from 'numeral';
import InlineEdit from 'react-edit-inline';
const TableRow = Table.TableRow;
const TableRowColumn = Table.TableRowColumn;
const displayName = 'Budget';
const propTypes = {
budget : PropTypes.object.isRequired
};
export default class Budget extends Component {
constructor( props, context ) {
super(props, context);
console.log('Budget this:', this.props);
this.handleBudgetChange = this.handleBudgetChange.bind(this);
}
validateBudget( num ) {
return (Number(num) > 0 && Number(num) < 100 && !Number.isNaN(num));
}
handleBudgetChange( num ) {
const inputNum = Number(num.budget) > 1 ? Number(num.budget) / 100 : Number(num.budget);
const budgetId = this.props.budget._id;
this.props.updateBudget(budgetId, inputNum);
}
render() {
const cellStyle = {
textAlign : 'center'
};
const editCellStyle = {
cursor : 'pointer',
textDecoration : 'underline'
};
const { department, owner, budget } = this.props.budget;
const editControl = (
<InlineEdit
validate={this.validateBudget}
change={this.handleBudgetChange}
text={numeral(this.props.budget.budget).format('0.0%')}
paramName="budget"
activeClassName="editing"
/>
);
return (
<TableRow style={cellStyle}
displayBorder={false}>
<TableRowColumn>{department}</TableRowColumn>
<TableRowColumn style={editCellStyle}>{editControl}</TableRowColumn>
<TableRowColumn>{owner.name}</TableRowColumn>
<TableRowColumn style={editCellStyle}>Edit</TableRowColumn>
</TableRow>
);
}
}
Budget.displayName = displayName;
Budget.propTypes = propTypes;
When I edit the budget in-line, the value changes for the state, but the component isn't re-rendering it.
Should I be connecting the <Budget /> component to the store and listening for changes here as well?
Or is there a better approach to what I'm trying to do?
Thank you for your help!
At a quick glance, is it just that you aren't passing the right prop down to your child component? For your DepartmentBudgets in the container, should you be mapping budgets to just 'budgets' and not 'budgets.budgets'?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With