bordeltabernacle
bordeltabernacle

Reputation: 1653

React.js - update component immediately on onChange event

TLDR: How do I get a component to update immediately on a textarea onChange event in another component?

I'm building a small JSON formatter app in order to learn React. I have an InputArea component to enter the JSON, and an OutputArea component to display the formatted JSON, both within my App component. I have a basic working example except that the formatted output is appearing one step behind it being entered into the textarea. I realise this is to do with setState and the fact that the state transition is pending rather than immediate. I believe I can use an updater callback with setState, or, as is recommended, use the componentDidUpdate lifecycle method, but I can't get my head around what I need to do in componentDidUpdate to get setState to update?

The code is not great, I'm just trying to get it working for now.

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      inputValue: "Paste your JSON here.",
      outputValue: "",
      hasError: false,
      errorValue: ""
    }

    this.handleSubmit = this.handleSubmit.bind(this)
    this.handleChange = this.handleChange.bind(this)
  }


  handleChange(event) {
    this.setState({ inputValue: event.target.value })
    let parsed = ""
    try {
      parsed = JSON.parse(this.state.inputValue)
    } catch (e) {
      this.setState({
        hasError: true,
        errorValue: e
      })
      return
    }
    const formatted = JSON.stringify(parsed, null, 2)
    this.setState({
      outputValue: formatted,
      hasError: false,
      errorValue: ""
    })
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1 className="App-title">Formatter-tat-tat</h1>
        </header>
        <InputArea
          value={this.state.inputValue}
          handleChange={this.handleChange}
        />
        <OutputArea
          hasError={this.state.hasError}
          value={this.state.outputValue}
          error={this.state.errorValue}
        />
      </div>
    )
  }
}

export default class InputArea extends Component {
  render() {
    return (
      <div className="input-area">
        <form>
          <label>
            JSON:
            <textarea
              value={this.props.value}
              onChange={this.props.handleChange}
            />
          </label>
        </form>
      </div>
    )
  }
}

export default class OutputArea extends Component {
  render() {
    if (this.props.hasError) {
      return (
        <div className="output-area">
          <p>Error: {this.props.error.toString()}</p>
        </div>
      )
    }
    return (
      <div className="output-area">
        <pre>{this.props.value}</pre>
      </div>
    )
  }
}

Upvotes: 0

Views: 819

Answers (1)

Maciej Sikora
Maciej Sikora

Reputation: 20152

You are one step before because exactly this code part:

1. this.setState({ inputValue: event.target.value })
2. let parsed = ""
3. try {
4.  parsed = JSON.parse(this.state.inputValue)
5. }
...

Lets go through above lines:

Line 1: You are using setState to push your component into new state, but as you said in the question it is not immediately action. So the change is not here yet.

Line 4: You are using the old state value, because as I've described in line 1 the change of the state is not reflected in this.state yet.

The fix: In the line 4 use event.target.value instead of out-dated this.state.inputValue

Upvotes: 1

Related Questions