kawnah
kawnah

Reputation: 3404

Change a state in react

I'm learning React and as a learning exercise am trying to do a very basic page where there is a form and you put text in an input box, you click submit and the header changes to what you entered. Here is my code so far:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {header: 'yeaheheh'}
  }
  changeHeader(e) {
    let newHeader = document.getElementById('input').value();
    e.preventDefault();
    console.log('submitted');
    this.setState(newHeader);
  }
  render() {
    return (
      <div>
            <h1>{this.state.header}</h1>
            <form onSubmit={this.changeHeader.bind(this)} className="change-header-form">
                <input id="input" type="text" placeholder="Enter Text Here" />
                <input type="submit" value="Submit" />
            </form>
          </div>  
    );
  }
}

export default App;

At first, when I clicked submit, nothing happened and I got an error in the console that says

Uncaught TypeError: Cannot read property 'setState' of null

I then realized I needed to bind the changeHeader function to this which I changed so before I had:

<form onSubmit={this.changeHeader}...

changed it to

<form onSubmit={this.changeHeader.bind(this)}...

After doing this, the error cleared but my header is still not updating.I read that there has been strong suggestions against changing state via setState is bad practice because calling setState() again could potentially alter the changed state. setState is also an asynchronous operation which would also explain why my header isn't changing.

With all that said, then what would be the best way to handle this? From what I understand, props wouldn't make sense either since those values are stored directly in your component and aren't parameters that can't be dynamically updated. I'm having a hard time understanding the relationship between these different data types and how they are handled in the DOM.

Upvotes: 0

Views: 15058

Answers (5)

Sushil Dhayal
Sushil Dhayal

Reputation: 87

You should modified your function like this..

constructor(props) {
    super(props); 
    _that = this;
  }

  changeHeader = (e) => {
     e.preventDefault();
    let newHeader = this.textInput.value;
    console.log('submitted');
    _that.setState({header : newHeader});
  }

Upvotes: 0

gaperton
gaperton

Reputation: 3826

There's the small bug in your code preventing it from working (this.setState(newHeader) -> this.setState({header: newHeader});), but the thing is that your code is not idiomatic for React.

You are supposed to use controlled components instead of grabbing the values from the form's inputs on submit, as you would do with jQuery.

"Controlled component" is a silly name for the pattern where an input's state is mapped to the application state, so an input itself behaves as if it would be kinda "stateless". In your case, you need to have separate component state member for every text input you've got. Input control should look like this:

<input value={ this.state.inputValue }
       onChange={ e => this.setState({ inputValue : e.target.value }) }
/>

Now it's bound to your inputValue state member, so you can just take it from the state at any moment you need. On form's submit handler, in your case.

That's it. Your code must be fixed accordingly. Refer to the "controlled components" manual for further details, it's the really important React concept.

Upvotes: 0

WitVault
WitVault

Reputation: 24130

You are setting state incorrectly. More over to get the data from input fields you can either use controlled input elements(via states) or uncontrolled input elements via "ref" which I have used in below example.

In controlled input element you store the value of input element in state and changes to that value is done by calling onChange method and then setting the state via this.setState({}).

Calling setState causes re-rendering to happen and dom gets the updated data based on new state.

Btw "refs" gets you the direct access to dom elements, in similar way $() was used in jquery and should be avoided if possible because it will lead to very hard to manage and predict dom changes.

Also there are cases where use of "refs" is recommended

There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.

  • Triggering imperative animations.

  • Integrating with third-party DOM libraries.

class App extends React.Component {
  constructor() {
    super();
    this.state = {header: 'yeaheheh'};
  }
  changeHeader = (e) => {
     e.preventDefault();
    let newHeader = this.textInput.value;
    console.log('submitted');
    this.setState({header : newHeader});
  }
  render() {
    return (
      <div>
            <h1>{this.state.header}</h1>
            <form onSubmit={this.changeHeader} className="change-header-form">
                <input id="input" ref={(input) => { this.textInput = input; }} type="text" placeholder="Enter Text Here" />
                <input type="submit" value="Submit" />
            </form>
          </div>  
    );
  }
}

ReactDOM.render(<App />, document.getElementById('test'));
<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="test">
</div>

Upvotes: 2

adam-beck
adam-beck

Reputation: 6009

Take a look at this article in the react docs: https://facebook.github.io/react/docs/forms.html#controlled-components.

Basically what you want to do is create another handler for the input. This will be called every time there is a change to the input field and a property in your state will be updated. Then, when you submit the form you can take that new property and "merge" it using setState to become the new header.

JS Bin

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      header: 'yeaheheh',
      next: ''
    }

    this.changeHeader = this.changeHeader.bind(this);
    this.updateNext = this.updateNext.bind(this);
  }

  changeHeader(e) {
    e.preventDefault();

    this.setState({
      header: this.state.next
    });
  }

  updateNext(e) {
    this.setState({
      next: e.target.value
    });
  }

  render() {
    return (
      <div>
        <h1>{this.state.header}</h1>
        <form onSubmit={this.changeHeader} className="change-header-form">
          <input id="input" type="text" placeholder="Enter Text Here" onChange={this.updateNext} />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app')); 

Maybe this bin will provide a little better context at what I'm trying to describe.

Upvotes: 0

Paul Rumkin
Paul Rumkin

Reputation: 6873

Replace this.setState(newHeader); with this.setState({header: newHeader});.

Upvotes: 2

Related Questions