I have created a React View, say MyView
, which has 2 text inputs whose initial values will be passed by parent read from a DB.
I also want the changed values to be saved back to DB. So, the view is also passed a callback function for the same.
Consider that DB save operation is heavy and you should not do it very frequently. So, I decided to listen to onBlur
events instead of onChange
events on the input boxes as onChange
is invoked on every key stroke.
First Approach:
class MyView extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="url" value={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
<input type="url" value={this.props.values.B}
onBlur={(evt)=>{this.props.saveValue('B', evt.target.value)}} />
<button type="button" onClick={this.props.resetValues}>Reset</button>
</div>
);
}
}
However, this does not work as React
enforces a controlled input (with value
attribute) always to be accompanied by an onChange
listener.
Second Approach:
So, I tried to make these inputs as uncontrolled
. That is, instead of value
attribute, used defaultValue
.
<input type="url" defaultValue={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
But this also did not work as on reset/clear button click, although the view was made to re-render but defaultValue
does not update once view is created.
Third Approach:
So, I finally added an onChange
listener but as no-op.
<input type="url" value={this.props.values.A}
onChange={()=>{console.log('do nothing')}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
Again, this did not work as the view re-renders after calling onChange
and since value is not reflected in props
yet, value
seems to reset back to initial on every key stroke.
Fourth Approach:
Last I tried was to maintain a state
in component and read value from state and on every onChange
save the value back to state. This worked to most extent but whenever there were external changes to props and the view was re-rendered, state
did not update. So, I added a getDerivedStateFromProps
function to view:
static getDerivedStateFromProps(props, state) {
return props.values;
}
Now, this again did not work. Reason being that this function is invoked even if I temporarily save values to state and the state was reset to initial values in props.
Can some ReactJS expert help me with my use-case?
Going by the comments on Liren Yeo's solution, I would handle the props-state reconciliation on componentDidUpdate
, where you get both the old state
and props
. This way you can determine how this.props
was updated and act accordingly. When the value in props
does not match state
nor oldProps
, the update is external and you should override the unsaved changes in the state.
The code should look something like this
componentDidUpdate(prevProps) {
if (this.props.values !== prevProps.values && this.props.values !== this.state.values) {
this.setState({values:this.props.values});
}
}
If you go this route, you can also leave the input uncontrolled
and update its value through a reference. This solves some unreliability with controlled
inputs, like for example, a type='number'
returning undefined
as its value when you type a decimal comma. You still need to store the value onChange
but only save it onBlur
and handling the state-prop-dom reconciliation in componentDidUpdate
You will still need onChange
to help you set the states of both url input. onBlur
is only used to trigger saving, it's 2 different events for different purposes.
Since your A & B values are passed down from parent component. MyView
's parent component should pass down this.state.values
and the functions to set the state.
Refer to this snippet if everything is in single component. You should be able move handleChange
function up to its parent component.
class App extends React.Component {
state = {
values: {
A: '',
B: ''
}
}
handleChange = e => {
this.setState({
values: {
...this.state.values,
[e.target.name]: e.target.value
})
}
handleBlur = e => {
if (e.target.name === 'A') {
alert(`Saving A: ${this.state.values.A}`)
}
if (e.target.name === 'B') {
alert(`Saving B: ${this.state.values.B}`)
}
}
render() {
return (
<div>
<label>Value A</label>
<input
type="url"
name="A"
value={this.state.values.B}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<label>Value B</label>
<input
type="url"
name="B"
value={this.state.values.A}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
</div>
EDIT: Your fourth approach should work with the following:
static getDerivedStateFromProps(props, state) {
return { values: props.values }
}
constructor(props) {
super(props)
this.state = {
values: props.values
}
}
so basically the this.state.values
is the final source of truth. When user types something, you setState
in this component and change it. But if props.values
changes (from external source), getDerivedStateFromProps
will update the values state.
So with the idea that onChange works, I would recommend you to take a look at this:
https://schier.co/blog/2014/12/08/wait-for-user-to-stop-typing-using-javascript.html Navigate to the heading: Wait for Typing to Stop
Hope it can somehow lead you to what you want to achieve.
来源:https://stackoverflow.com/questions/53479598/reactjs-save-input-value-on-blur-and-not-on-every-key-stroke