0

I have an App component which holds an input. Every time I type in the input, the value of the input updates and a Message component prints a different message, depending on how long the input is. At the same time, a third component called Character print to the screen every letter of the string, individually. The desired behavior is that when I click on one of the letters, it gets removed from the string, the new string is displayed on the screen and the input also gets updated with the new string.

I used some console.logs to debug and everything seems to be happening as expected, until the last step when I am trying to update the state, but for some reason, it doesn't get updated.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text: "" };
  }

  render() {
    const handleUpdateText = event => {
      this.setState({
        text: event.target.value
      });
    };

    const inputLength = this.state.text.length;
    const toArray = this.state.text.split("");

    const handleDeleteLetter = index => {
      toArray.splice(index, 1);
      console.log(toArray);
      const updatedArray = toArray.join("");
      console.log(updatedArray);
      this.setState({ text: updatedArray });
      console.log(this.state.text);
    };

    return (
      <>
        <input type="text" onChange={handleUpdateText} />
        <Message inputLength={inputLength} />

        {toArray.map((letter, index) => (
          <Character
            key={index}
            theLetter={letter}
            deleteLetter={() => handleDeleteLetter(index)}
          />
        ))}
      </>
    );
  }
}

class Message extends React.Component {
  render() {
    const { inputLength } = this.props;

    let codeToPrint = "The text is long enough!";

    if (inputLength <= 5) {
      codeToPrint = "The text is not long enough!";
    }
    return <p>{codeToPrint}</p>;
  }
}

class Character extends React.Component {
  render() {
    const { theLetter, deleteLetter } = this.props;
    return (
      <div
        style={{
          display: "inline-block",
          padding: "16px",
          textAlign: "center",
          margin: "16px",
          backgroundColor: "tomato"
        }}
        onClick={deleteLetter}
      >
        {theLetter}
      </div>
    );
  }
}

The complete code is here:

https://codesandbox.io/s/react-the-complete-guide-assignment-2-list-conditionals-e6ty6?file=/src/App.js:51-1007

I don't really understand what am I doing wrong and I have a feeling is somehow related to a life cycle method. Any answer could help. Thank you.

Kasia
  • 118
  • 3
  • 16
  • 1
    In addition to the provided answer, do not log your state where you update it since setting the state is async. You can log the state in the `render` method. Also, define your methods as a `class` method instead of defining them in the `render` method. One more thing, try to avoid using an index as your key :) – devserkan Jun 27 '20 at 20:29
  • This actually answers the question why is the state not the current one but the previous, when I log it. I'm not sure what you mean by "Also, define your methods as a class method instead of defining them in the render method." And yes, I know using the index as key is a bad idea, I just used it here for the sake of example. The input is much appreciated. – Kasia Jun 27 '20 at 20:55
  • 1
    The answer owner updated their answer after my comment. There is an example of how can you do it. Do not define your functions in the `render` method, move them to the class field, for arrow functions drop the `const`, for regular functions drop the `function` part. – devserkan Jun 27 '20 at 21:07

1 Answers1

1

State is getting updated, you just need to pass value prop to the input so that input's value can be in sync with your state

<input type="text" value={this.state.text} onChange={handleUpdateText} />

And you're not seeing updated state just after setting it because setState is asynchronous. That's why the console statement just after the setState statement shows the previous value.

Also you should move functions out of your render method, because everytime your component re-renders, new functions would be created. You can declare them as class properties and pass their reference

   handleUpdateText = event => {
      this.setState({
        text: event.target.value
      });
    };

render() {
.......
 return (
      <>
        <input type="text" onChange={this.handleUpdateText} />
Siddharth
  • 1,200
  • 8
  • 13
  • I didn't even notice I didn't set a value for the input. How come it was getting updated anyway, since the value wasn't set, therefore it wasn't connected with the state (if I remove the value property and I type inside the input, it displays exactly what I typed). Thank you – Kasia Jun 27 '20 at 20:57
  • 1
    It displays what is your state actually not the value of your input. If you don't set a `value`, your input is not going to be a controlled one. When you set a `value` there and tie it to your state, this makes them sync and your input a controlled one. You can test this. Without `value` write a long word in input, then delete most of it (letters) via your component and go to input and continue to writing. Your display will reflect what input holds immediately. – devserkan Jun 27 '20 at 21:14
  • 2
    Oh, you already tested it :) The input has its value here, it is not the state. With this method, you are tying the state with the value. So, even if you don't set a value there the input will have it. You can see the [controlled](https://reactjs.org/docs/forms.html#controlled-components) and [uncontrolled](https://reactjs.org/docs/uncontrolled-components.html) components part in the docs. – devserkan Jun 27 '20 at 21:24