I am having trouble with a React form and managing the state properly. I have a time input field in a form (in a modal). The initial value is set as a state variable in getInitialState
, and is passed in from a parent component. This in itself works fine.
The problem comes when I want to update the default start_time value through the parent component. The update itself happens in the parent component through setState start_time: new_time
. However in my form, the default start_time value never changes, since it is only defined once in getInitialState
.
I have tried to use componentWillUpdate
to force a change in state through setState start_time: next_props.start_time
, which did actually work, but gave me Uncaught RangeError: Maximum call stack size exceeded
errors.
So my question is, what's the correct way of updating state in this case? Am I thinking about this wrong somehow?
Current Code:
@ModalBody = React.createClass
getInitialState: ->
start_time: @props.start_time.format("HH:mm")
#works but takes long and causes:
#"Uncaught RangeError: Maximum call stack size exceeded"
componentWillUpdate: (next_props, next_state) ->
@setState(start_time: next_props.start_time.format("HH:mm"))
fieldChanged: (fieldName, event) ->
stateUpdate = {}
stateUpdate[fieldName] = event.target.value
@setState(stateUpdate)
render: ->
React.DOM.div
className: "modal-body"
React.DOM.form null,
React.createElement FormLabelInputField,
type: "time"
id: "start_time"
label_name: "Start Time"
value: @state.start_time
onChange: @fieldChanged.bind(null, "start_time”)
@FormLabelInputField = React.createClass
render: ->
React.DOM.div
className: "form-group"
React.DOM.label
htmlFor: @props.id
@props.label_name + ": "
React.DOM.input
className: "form-control"
type: @props.type
id: @props.id
value: @props.value
onChange: @props.onChange
This question is related to
reactjs
react-state
I think use ref is safe for me, dont need care about some method above.
class Company extends XComponent {
constructor(props) {
super(props);
this.data = {};
}
fetchData(data) {
this.resetState(data);
}
render() {
return (
<Input ref={c => this.data['name'] = c} type="text" className="form-control" />
);
}
}
class XComponent extends Component {
resetState(obj) {
for (var property in obj) {
if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
if ( obj[property] !== this.data[property].state.value )
this.data[property].setState({value: obj[property]});
else continue;
}
continue;
}
}
}
From react documentation : https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
Erasing state when props change is an Anti Pattern
Since React 16, componentWillReceiveProps is deprecated. From react documentation, the recommended approach in this case is use
ParentComponent
of the ModalBody
will own the start_time
state. This is not my prefer approach in this case since i think the modal should own this state. start_time
state from your ModalBody
and use getInitialState
just like you have already done. To reset the start_time
state, you simply change the key from the ParentComponent
Use Memoize
The op's derivation of state is a direct manipulation of props, with no true derivation needed. In other words, if you have a prop which can be utilized or transformed directly there is no need to store the prop on state.
Given that the state value of start_time
is simply the prop start_time.format("HH:mm")
, the information contained in the prop is already in itself sufficient for updating the component.
However if you did want to only call format on a prop change, the correct way to do this per latest documentation would be via Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization
The new hooks way of doing this is to use useEffect instead of componentWillReceiveProps the old way:
componentWillReceiveProps(nextProps) {
// You don't have to do this check first, but it can help prevent an unneeded render
if (nextProps.startTime !== this.state.startTime) {
this.setState({ startTime: nextProps.startTime });
}
}
becomes the following in a functional hooks driven component:
// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
//
useEffect(() => {
if (props.startTime !== startTime) {
setStartTime(props.startTime);
}
}, [props.startTime]);
we set the state using setState, using useEffect we check for changes to the specified prop, and take the action to update the state on change of the prop.
// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
//
useEffect(() => {
if (props.startTime !== startTime) {
setStartTime(props.startTime);
}
}, [props.startTime]);
Can this method be migrated to class components?
componentWillReceiveProps
is being deprecated because using it "often leads to bugs and inconsistencies".
If something changes from the outside, consider resetting the child component entirely with key
.
Providing a key
prop to the child component makes sure that whenever the value of key
changes from the outside, this component is re-rendered. E.g.,
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
On its performance:
While this may sound slow, the performance difference is usually insignificant. Using a key can even be faster if the components have heavy logic that runs on updates since diffing gets bypassed for that subtree.
There is also componentDidUpdate available.
Function signatur:
componentDidUpdate(prevProps, prevState, snapshot)
Use this as an opportunity to operate on the DOM when the component has been updated. Doesn't get called on initial render
.
See You Probably Don't Need Derived State Article, which describes Anti-Pattern for both componentDidUpdate
and getDerivedStateFromProps
. I found it very useful.
It's quite clearly from their docs:
If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.
Use: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization
You Probably Don't Need Derived State
1. Set a key from the parent
When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
2. Use getDerivedStateFromProps
/ componentWillReceiveProps
If key doesn’t work for some reason (perhaps the component is very expensive to initialize)
By using getDerivedStateFromProps
you can reset any part of state but it seems
a little buggy at this time (v16.7)!, see the link above for the usage
Apparently things are changing.... getDerivedStateFromProps() is now the preferred function.
class Component extends React.Component {_x000D_
static getDerivedStateFromProps(props, current_state) {_x000D_
if (current_state.value !== props.value) {_x000D_
return {_x000D_
value: props.value,_x000D_
computed_prop: heavy_computation(props.value)_x000D_
}_x000D_
}_x000D_
return null_x000D_
}_x000D_
}
_x000D_
Source: Stackoverflow.com