Reputation: 13398
I've build a text input component that allows the user to input a number into a text box. The component calls it's onChange
prop if the user enters a valid number, and also sets its state to the string they entered. This is so that if the user enters "-" or "." by itself, the component can keep "-" or "." in the text box while the user enters the rest of their number.
When they finally do enter a valid number, it is parsed and passed to the onChange
prop. The parent then sets its own internal state, and passes the parsed number back in the value
prop.
The parent may also send a new number in the value
prop for reasons other than user input, so I need to implement componentWillReceiveProps
to overwrite the state with the new value when it is received.
This leads to a new problem: When the user enters "0." into the input box, it successfully parses to the number 0, which is passed to the parent via onChange
prop, then passed back to the child via value
prop. The component then calls toString()
on it, which results in the string "0", which overwrites the "0." and makes it impossible to enter numbers with a decimal.
To solve that problem, the component first checks if the value passed in the value
prop is equal to the string in the state, after parsing it (basically, if the two values are equal after canonicalization) and if they are, it doesn't override the value in the state, so that the string displayed to the user will remain unchanged.
But, inside componentWillReceiveProps
, the state is the same as the old version of the state, and doesn't reflect the new characters the user has typed. It seems that operations are happening in this order:
onChange
prop to pass new number up to parent.value
prop (as a number).componentWillReceiveProps
is calledThe problem is caused by #7 happening before #8 (presumably, I haven't dug into React source to verify this). I've solved this problem by modifying this.state
directly in the callback when the user types a character, as well as using setState
with the same value, but I know that modifying this.state
directly should be avoided if possible.
So my question is: can I solve this problem without modifying this.state
directly? If so, how?
Upvotes: 1
Views: 1207
Reputation: 1151
Edit: I just realized that I totally missed the point of your question. :|
You could do something like this:
const Form = React.createClass({
getInitialState: function() {
return {
number: {
txt: null, // the text that displays in the input box
val: null // the actual number value
}
};
},
componentWillReceiveProps: function(nextProps) {
// assume that some `parseInput` function exists to get the number value
let numberVal = parseInput(nextProps.number);
if (numberVal !== this.state.number.val) {
this.setState({
number: {
txt: numberVal,
val: numberVal
}
});
}
},
handleChange: function(newTxt) {
// update number input
let newNumberState = {
txt: newTxt
};
// assume that some `parseInput` function exists to get the number value
let newVal = parseInput(newTxt);
if (newVal !== this.state.number.val) {
newNumberState.val = newVal;
tellSomeoneThatWeHaveANewNumber(newVal);
}
// update state (either txt or txt & val)
this.setState({
number: newNumberState
});
},
render: function() {
return (
<NumberInput
onInputChange={this.handleChange}
value={this.state.number.txt}
/>
);
}
});
const NumberInput = React.createClass({
handleChange: function(e) {
// filter out unwanted characters
const newValue = e.target.value.replace(/[^\d.-]/g, '');
// send new value to parent component
this.props.onInputChange(newValue);
},
render: function() {
let {value} = this.props;
return (
<input
onChange={this.handleChange}
type="text"
value={value || ''}
/>
);
}
});
Upvotes: 1