gamda gamda - 2 months ago 9
React JSX Question

React input not updating when receiving props

Fix

Add this method for the first time that the element is rendered, it has access to the

props
received right away.

componentDidMount() {
if (typeof this.props.options.value !== "undefined")
this.setState({value: this.props.options.value})
},


I have several react elements that are almost working as intended. I have a view with a list of elements and a modal view to edit them. When the edit button of an element is clicked, the element is set on the state as selected, and the modal form is shown (it is passed the element as a prop).

showUpdateForm(poiIndex) {
this.setState({selectedPoi: this.state.pois[poiIndex]})
$('#updatePoiModal').modal('show')
console.log('shown')
},

...render()...
<UpdatePoiForm poi={this.state.selectedPoi} ref="updatePoiForm" successHandler={this.successUpdateHandler}/>


When an item is clicked the modal view is displayed correctly, however, the text values passed are not displayed; the ones from the previous selected element are shown (or empty boxes if it is the first element clicked). Here is the render method of the modal view:

render() {
console.log('rendering')
if(!this.props.poi)
return null
console.log(this.props.poi.name)
var poi = this.props.poi

var config = {
url: `${context.apiUrl}user/pois/${poi.id}`,
method: 'PUT',
successMessage: 'El Punto de Interés fue actualizado',
successHandler: this.successHandler,
hideSubmitButton: true,
fields: {
name: {
label: 'Nombre',
type: 'text',
value: this.props.poi.name
},
location: {
label: 'Ubicación',
type: 'text',
display: false,
value: this.props.poi.location
},
category_id: {
label: 'Categoría',
type: 'select',
options: context.poi_categories.map((cat) => {
return {value: cat.id, desc: cat.name}
})
}
}
}

return (
<div>
<Row>
<Col size="12">
<Panel>
<PanelBody>
<FormComponent config={config} ref="form"/>
</PanelBody>
</Panel>
</Col>
<Col size="12">
<Panel>
<PanelHeading heading="Selecciona la ubicación" />
<Map height="400px" ref="map_update_poi" zoom="15"/>
</Panel>
</Col>

</Row>
</div>
)
}


Here is the render method of the form component:

render() {
elems = []
count = 0

for (var key in this.props.config.fields) {
console.log(this.props.config.fields[key])
var _type = this.props.config.fields[key].type
var config = {
key: count++,
options: this.props.config.fields[key],
ref: key,
refName: key,
fullWidth: this.props.config.fullWidth,
}

switch (_type) {
case 'text':
elems.push(<InputElement {...config}/>)
break
case 'select':
elems.push(<SelectElement {...config}/>)
break
case 'multipleSelect':
elems.push(<MultipleSelectElement {...config}/>)
break
case 'button':
elems.push(<ButtonElement {...config}/>)
break
case 'switcher':
elems.push(<SwitcherElement {...config}/>)
break
case 'timePicker':
elems.push(<TimePickerElement {...config}/>)
break
case 'radio':
elems.push(<RadioElement {...config}/>)
break
case 'autocomplete':
elems.push(<AutoCompleteInputElement {...config}/>)
break
}
}
console.log(elems)

return (
<form action="#" className="form-horizontal">
<div className="form-content">
{elems}
{!this.props.config.hideSubmitButton ? <div className="form-buttons">
<div className="row">
<div className="col-md-offset-3 col-md-9">
<button type="submit" onClick={this.handleSave}
className="btn btn-blue btn-ripple">Crear</button>
</div>
</div>
</div> : null}
</div>
</form>
)
}


And the render method of the input element (removed variable declarations for brevity, none are missing):

render() {
return (
<div className={formClass} style={style}>
<label className="control-label col-md-3">{this.props.options.label}</label>
<div className={inputClass}>
<div className="inputer">
<div className="input-wrapper">
<input ref={this.props.refName} type="text" className="form-control" onFocus={this.props.onFocus}
placeholder={this.props.placeholder} value={this.state.value} onChange={this.handleChange}/>
</div>
{validationMsg}
</div>
</div>
</div>
)
}


Lastly, I am checking the input props here:

componentWillReceiveProps(nextProps) {
console.log('input element', nextProps)
if (typeof this.props.options.value !== "undefined")
this.setState({value: this.props.options.value})
},


I have added log statements in every "stop" of the chain. The modal view shows the element correctly, the form shows the strings of the last element clicked, and the props of the input element also show information of the most recently clicked element. However, when input is rendered,
this.state.value
is null (which is the value given in getInitialState()) or the value of the previous element. According to the order of logs, the props are received before rendering. While I understand that state updates are not immediate, if the render happened, then the change in state should trigger render again, with the correct values this time, but it is not happening and I don't understand why!

shown
list index 1
name Object { label="Nombre", type="text"}
location Object { label="Ubicación", type="text", display=false}
category_id Object { label="Categoría", type="select", options=[2]}
[Object { key="0", ref="name", props={...}, more...}, Object { key="1", ref="location", props={...}, more...}, Object { key="2", ref="category_id", props={...}, more...}]
input props Object { options={...}, refName="name", fullWidth=undefined}
render input null
input props Object { options={...}, refName="location", fullWidth=undefined}componentWillReceiveProps index.js (line 35220)
render input null
** At this point the new element is received **
** The objects didn't paste completely, but the correct values are
there this time. However, there is no output from componentWillReceiveProps **
new poi test02
rendering update form
update form test02
name Object { label="Nombre", type="text", value="test02"}
location Object { label="Ubicación", type="text", display=false, more...}
category_id Object { label="Categoría", type="select", options=[2]}
[Object { key="0", ref="name", props={...}, more...}, Object { key="1", ref="location", props={...}, more...}, Object { key="2", ref="category_id", props={...}, more...}]
2 render input null
Object { category="Almacen", category_id=1, id=627, more...}


Edit 2:
Now this problem only occurs on the first click. Happened when I replaced
this.props
for nextProps per @Radio- 's suggestion.

Answer

In componentWillReceiveProps, set the state to nextProps.options.value instead of this.props.options.value

componentWillReceiveProps(nextProps) {
  if (typeof nextProps.options.value !== "undefined")
    this.setState({value: nextProps.options.value})
},

If it's on the first render, componentWillReceiveProps isn't called, so you can use componentDidMount with this.props to achieve the same:

componentDidMount() {
   if (typeof this.props.options.value !== "undefined")
   this.setState({value: this.props.options.value})
}
Comments