1

I'm a javascript lover but I'm a fresh React beginner. I've got some trouble understanding the React logic so I've made a simple example, an app with a numeric input and a cancel button that cancels last action:

class App extends React.Component {    //A simple numeric input and a cancel button
  constructor(props){
    super(props);
    this.state={
      selectedNumber:0,
      lastNumber:null
    }
  }
  render() {
    return (
      <div className="App">
        <input type="number" value={this.state.selectedNumber} onChange={this.onNumberChange}></input>
        <button disabled={this.state.lastNumber===null} onClick={this.onUndo}>Undo last action</button>
      </div>
    );
  }
  onNumberChange=(e)=>{
    let prevNumber=this.state.selectedNumber;
    this.setState({selectedNumber:e.target.value, lastNumber:prevNumber});
  }
  onUndo=(e)=>{
    let prevNumber=this.state.lastNumber;
    this.setState({selectedNumber:prevNumber,lastNumber:null});
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <div id="root">
  </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</body>
</html>

Everything works fine but let's say the user enters the number 200 (by typing), and then want to undo this value and retrieve the last one, it will fallback to 20 because each keyboard entry refresh the state with the input value...

To avoid this behaviour I tried to move the input into a custom component, add local state to it and renders the input value according to the component props. I added a onChange event locally that set the component state accordingly to user "live" typing, and I binded a onBlur event to the parent's callback that sets its own state accordingly to "complete" user entry.

The component renders well initially, renders well when you type in it, but doesn't update when changed externally by the cancel button...

class App extends React.Component {    //A simple numeric input and a cancel button
  constructor(props){
    super(props);
    this.state={
      selectedNumber:0,
      lastNumber:null
    }
  }
  render() {
    return (
      <div className="App">
        <MyNumericInput selectedNumber={this.state.selectedNumber} onBlur={this.onBlur}/>
        <button disabled={this.state.lastNumber===null} onClick={this.onUndo}>Undo last action</button>
      </div>
    );
  }
  onBlur=(e)=>{
    let prevNumber=this.state.selectedNumber;
    this.setState({selectedNumber:e.target.value, lastNumber:prevNumber});
  }
  onUndo=(e)=>{
    let prevNumber=this.state.lastNumber;
    this.setState({selectedNumber:prevNumber,lastNumber:null});
  }
}
class MyNumericInput extends React.Component {
  constructor(props){
    super(props);
    this.state={
      selectedNumber:props.selectedNumber
    }
  }
  render() {
  return(
    <input type="number" value={this.state.selectedNumber} onChange={this.onChange} onBlur={this.props.onBlur}/>);
  }
  onChange=(e)=>{
    this.setState({selectedNumber:e.target.value});
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <div id="root">
  </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</body>
</html>
LePioo
  • 137
  • 1
  • 11

2 Answers2

0

I think you can do this.

onBlur = () => {
  this.setState(prevState => ({ lastNumber: prevState.selectedNumber }));
}

onChange = (e) => {
  this.setState({ selectedNumber: e.target.value });
}

Now, everytime the focus in blurred from the input, your lastNumber is updated. And while clicking undo button it should work fine. I hope it works. Happy coding :)

bjupreti
  • 353
  • 3
  • 6
  • I tryed your code but the cancel button doesn't work anymore... Ps: I edited the snippet above so it's testable now – LePioo Jan 19 '19 at 18:23
0

I've found a solution on this very forum...

stackoverflow.com/questions/53479598/reactjs-save-input-value-on-blur-and-not-on-every-key-stroke

BUT React documentation tells that it's not a good practice...

You may call setState() immediately in componentDidUpdate() but [...] it would also cause an extra re-rendering which, while not visible to the user, can affect the component performance. If you’re trying to “mirror” some state to a prop coming from above, consider using the prop directly instead.

I put my updated code with the solution given on the link above, can you confirm that this approach is not good and what is the alternative?

class App extends React.Component {    //A simple numeric input and a cancel button
  constructor(props){
    super(props);
    this.state={
      selectedNumber:0,
      lastNumber:null
    }
  }
  render() {
    return (
      <div className="App">
        <MyNumericInput selectedNumber={this.state.selectedNumber} onBlur={this.onBlur}/>
        <button disabled={this.state.lastNumber===null} onClick={this.onUndo}>Undo last action</button>
      </div>
    );
  }
  onBlur=(e)=>{
    let prevNumber=this.state.selectedNumber;
    this.setState({selectedNumber:e.target.value, lastNumber:prevNumber});
  }
  onUndo=(e)=>{
    let prevNumber=this.state.lastNumber;
    this.setState({selectedNumber:prevNumber,lastNumber:null});
  }
}
class MyNumericInput extends React.Component {
  constructor(props){
    super(props);
    this.state={
      selectedNumber:props.selectedNumber
    }
  }
  render() {
  return(
    <input type="number" value={this.state.selectedNumber} onChange={this.onChange} onBlur={this.props.onBlur}/>);
  }
  onChange=(e)=>{
    this.setState({selectedNumber:e.target.value});
  }
  componentDidUpdate(prevProps) {
    if (this.props.selectedNumber !== prevProps.selectedNumber && this.props.selectedNumber !== this.state.selectedNumber) {
      this.setState({selectedNumber: this.props.selectedNumber});
    }
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <div id="root">
  </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</body>
</html>
LePioo
  • 137
  • 1
  • 11