I have a parent component and a child component. The child component initially renders data into a form but upon changing the data, the child component does not update.
Parent Component:
import React from 'react'
import { connect } from 'react-redux'
import styles from '../styles'
import ExpressionsForm from './expressionsForm'
class EditCondition extends React.Component {
constructor (props) {
super(props)
this.state = {
condition: null
}
this.updateExpression = this.updateExpression.bind(this)
this.changes = false
}
componentWillMount () {
let conditionid = this.props.data.id
let condition = this.props.conditions.find(c => {
return (c.id = conditionid)
})
this.setState({ condition })
}
updateExpression (e) {
let expressionid = e.currentTarget.dataset.expressionid
let field = e.currentTarget.dataset.field
let value = e.target.value
let condition = this.state.condition
let expression = condition.expressions[expressionid]
expression[field] = value
condition.expressions[expressionid] = expression
this.changes = true
this.setState({ condition })
console.log('updateExpression condition: ', condition)
}
render () {
let condition = this.state.condition
if (!this.state.condition) {
return (
<div>
The selected condition with ID "{this.props.data.id}" did not load. It
may not exist. Refresh and try again.
</div>
)
}
let groupOptions = this.props.gambitGroups.map(g => {
return (
<option value={g.id} key={'group' + g.id}>
{g.name}
</option>
)
})
console.log('RENDER editCondition: ', condition) // <-- Note: This always logs as expected
let expressionsJSX = condition.expressions.map((expression, i) => {
expression.id = i
console.log('expression: ', expression) // <-- Note: This always logs as expected
return (
<ExpressionsForm
key={'expressionsForm_' + i}
expression={expression}
deleteExpression={this.deleteExpression}
updateExpression={this.updateExpression}
updateExpressionData={this.updateExpressionData}
/>
)
})
return (
<table>
<thead>
<tr>
<th {...styles.modal.tableHeaderLeftAlign}>
Device & Data Point
</th>
<th {...styles.modal.tableHeaderLeftAlign}>Operator</th>
<th {...styles.modal.tableHeaderLeftAlign}>Value</th>
<th {...styles.modal.tableHeaderLeftAlign}>PlateValue</th>
<th {...styles.modal.tableHeaderLeftAlign}> </th>
</tr>
</thead>
<tbody>{expressionsJSX}</tbody>
</table>
)
}
}
export default connect(
(state, ownProps) => ({
user: state.user,
users: state.users,
gambitGroups: state.gambitGroups,
// deviceGroups: state.deviceGroups,
conditions: state.conditions,
reactions: state.reactions,
setEditMode: ownProps.setEditMode,
navByName: ownProps.navByName
}),
dispatch => ({
addImage: file => dispatch({ type: 'UPDATE_CONDITION_LOGO', file }),
updateCondition: condition =>
dispatch({ type: 'UPDATE_CONDITION', condition })
})
)(EditCondition)
And Child Component:
import React from 'react'
import { connect } from 'react-redux'
import styles from '../styles'
class ExpressionsForm extends React.Component {
constructor (props) {
super(props)
this.state = {}
this.updateExpression = this.updateExpression.bind(this)
}
updateExpression (e) {
this.props.updateExpression(e)
}
render () {
let expression = this.props.expression
console.log('expression: ', expression) // Note: logs initial render only.
let data = expression.data
let deviceId = data.deviceId
let dataPointIndex = data.dataPointIndex
let operator = expression.operator
let plateValue = expression.plateValue
let value = expression.value
console.log('RENDER expressionForm: ', expression) // Note: logs initial render only
let deviceOptions = this.props.devices.map((device, i) => {
return (
<option value={device.id} key={'device_' + i}>
{device.userAssignedName}
</option>
)
})
let dataPointOptions = this.props.devices[0].inputs.map((input, i) => {
return (
<option value={input.id} key={'input_' + i}>
{input.name} currentValue: {input.value}
</option>
)
})
let operatorOptions = ['==', '!=', '<=', '>=', '<', '>'].map(
(operator, i) => {
return (
<option value={operator} key={'operator_' + i}>
{operator}
</option>
)
}
)
return (
<tr>
<td>
<select
{...styles.modal.inputSexy}
style={{ marginBottom: '20px' }}
data-field='deviceid'
data-expressionid={expression.id}
value={deviceId}
onChange={this.updateExpressionData}
>
<option value=''></option>
{deviceOptions}
</select>
<select
{...styles.modal.inputSexy}
data-field='dataPointIndex'
data-expressionid={expression.id}
value={dataPointIndex}
onChange={this.updateExpressionData}
>
<option value=''></option>
{dataPointOptions}
</select>
</td>
<td>
<select
{...styles.modal.inputSexy}
style={{ width: '75px' }}
data-field='operator'
data-expressionid={expression.id}
value={operator}
onChange={this.updateExpression}
>
<option value=''></option>
{operatorOptions}
</select>
</td>
<td>
<input
{...styles.modal.inputSexy}
style={{ width: '50px' }}
data-field='value'
data-expressionid={expression.id}
value={value}
onChange={this.updateExpression}
/>
</td>
<td>
<input
{...styles.modal.inputSexy}
style={{ width: '88px' }}
data-expressionid={expression.id}
data-field='plateValue'
value={plateValue}
onChange={this.updateExpression}
/>
</td>
<td>
<i className='fa fa-close'
data-expressionid={expression.id}
onClick={this.deleteExpression}
></i>
</td>
</tr>
)
}
}
export default connect(
(state, ownProps) => ({
user: state.user,
users: state.users,
devices: state.devices,
gambitGroups: state.gambitGroups,
// deviceGroups: state.deviceGroups,
conditions: state.conditions,
reactions: state.reactions,
setEditMode: ownProps.setEditMode,
navByName: ownProps.navByName
}),
dispatch => ({
addImage: file => dispatch({ type: 'UPDATE_XXX', file })
})
)(ExpressionsForm)
I have an array of objects in the redux store called Conditions. The parent component gets an ID of one of these conditions, finds the correct condition, and loads it into state via componentWillMount to be modified by the user. The condition has an array of objects on it called expressions. Each of these expressions are passed to the child component called ExpressionsForm.
so we loop over the expressions via the map function and return the resulting JSX as expressionsJSX.
let expressionsJSX = condition.expressions.map((expression, i) => {
expression.id = i
console.log('expression: ', expression) // <-- Note: This always logs as expected
return (
<ExpressionsForm
key={'expressionsForm_' + i}
expression={expression}
deleteExpression={this.deleteExpression}
updateExpression={this.updateExpression}
updateExpressionData={this.updateExpressionData}
/>
)
})
Note that the has the expression passed to it expression={expression}
And in the child component's render you see
let expression = this.props.expression
console.log('expression: ', expression) // Note: logs initial render only.
Since this is a prop, whether it's being console.log'd or rendered into some JSX doesn't matter - when the prop changes the changes should also be re-rendered. BUT it's not doing it in this case. Why?
For example, I have 1 expression saved on 1 condition. It renders, I click into the plateValue input field of the expression - which contains a 5 by default - and attempt to add a 6 after the 5. When the parent component updates state an re-renders I see the in the console.log's that the expression's plateValue field now contains a '56'...it just doesn't render in the child component....!?
Here is an example console.log
Initial Render:
RENDER editCondition: {id: "1", group: 1, name: "Temperature >= 75F", meta: "If >= 75F in greenhouse turn on AC until 5 degrees cooler than 75F", expressions: Array(1)} editCondition.jsx:191 expression: {data: {…}, operator: ">=", value: "75", plateValue: "5", id: 0} expressionsForm.jsx:39 RENDER expressionForm: {data: {…}, operator: ">=", value: "75", plateValue: "5", id: 0}
Click into plateValue field and add a '6', parent rerenders...and:
editCondition.jsx:188 RENDER editCondition: {id: "1", group: 1, name: "Temperature >= 75F", meta: "If >= 75F in greenhouse turn on AC until 5 degrees cooler than 75F", expressions: Array(1)} editCondition.jsx:191 expression: {data: {…}, operator: ">=", value: "75", plateValue: "56", id: 0} editCondition.jsx:153 STATE SET! updateExpression condition: {id: "1", group: 1, name: "Temperature >= 75F", meta: "If >= 75F in greenhouse turn on AC until 5 degrees cooler than 75F", expressions: Array(1)}
I see a 'plateValue: "56"' in there. So why isn't it rerendering in the child component? So confused.
I've tried componentWillReceiveProps, componentWillUpdate, et al. I can't even get these to fire off a console.log.
Something is going on that I can't figure out. I've been doing React for a long time and I'm stumped. That doesn't happen very often anymore.
Thanks in advance for your help
PS I did look at getDerivedStateFromProps - It's great that the documentation provides examples, but they don't explain what the props and state parameters actually are. The docs suck. Their explanation sucks. Their example doesn't illustrate what it actually does. I only ever use componentWillReceiveProps to know when a prop has changed, and then update state or whatever. getDerivedStateFromProps just confuses me. None the less I played around with it and couldn't get it to work either.
