2

I tried to make a small program that has an input field where you can insert text. Then when you click save, it saves the text and shows you below. My question is that, initially the text was not showing up after I clicked 'save', but only when I made another change to the input field after clicking 'save'. After adding this.setState({inputText: ' '}) in saveValue, it started to work but I'm not so sure why.

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

class App extends Component {
  state = {
    inputText: '',
    savedValues: [],
  }

  textStorage = (event) => {
    this.setState({
      inputText: event.target.value
    })
  }

  saveValue = (inputText) => {
    this.state.savedValues.push(this.state.inputText)
    this.setState({inputText: ' '}) 
  }
  

  render() {  

  
    return (
      <div className = "App"> 
        <h1>Hello World</h1>
        <input 
          type = "text"
          value = {this.state.inputText}
          onChange = {(event) => this.textStorage(event)}
        />
        <p>Here's your text: {this.state.inputText}</p>
        <button onClick = {this.saveValue}>Save</button>
        <p>{this.state.savedValues.join('')}</p>
        <div>
          <img className = "Picture" src={picture} alt="Picture"/>
        </div>
      </div>
      

    )
  }


}

export default App;

1 Answer 1

4

Issue

You are mutating your state object when you push a value into it. You aren't returning a new array state reference so react doesn't rerender until you actually update state by updating the input value and trigger the rerender. Adding the update to clear the input value was the state update that triggered a rerender.

saveValue = (inputText) => {
  this.state.savedValues.push(this.state.inputText) // <-- mutation!
  this.setState({inputText: ' '}) 
}

Solution

Use a correct state update. array.prototype.concat concatenates a value to and returns a new array reference.

saveValue = () => {
  this.setState(prevState => ({
    savedValues: prevState.savedValues.concat(prevState.inputText),
    inputText: ' ',
  })) 
}

Alternatively you can use the Spread syntax to create a new array as well.

saveValue = () => {
  this.setState(prevState => ({
    savedValues: [...prevState.savedValues, prevState.inputText],
    inputText: ' ',
  })) 
}
Sign up to request clarification or add additional context in comments.

8 Comments

But he is not mutating a state state object. Look how is declaring it state = {. I think for it to be a state object, it would have to be declared in a constructor and with the this e.g. this.state = {.... state seems to simply be a global variable in his class, no?
@codemonkey React state is a class property (in class-based components), you don't need a constructor just to set initial state. The mutation is pushing into the array, thus mutating the contents of the array while the array itself still has the same reference.
Got it. It's just the funny thing is his code works as is with no issues.
@AryanV That is just the syntax for a functional state update, used when the next state depends on the previous state. prevState (or any name you give this variable) is just the previous state value, so you can operate on the previous state and return the next state object. You should be able to do something like this.setState(state => state + 1) for example, if you are running a counter. You can read more about them in the official docs.
@AryanV You're correct. I was mistakenly appending the save button's onClick event instead of the inputText state value. I amended my answer and use prevState.inputtext and removed the event object argument from the handler. Here's a codesandbox you can test it in.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.