Manage State Locally First (React Redux examples)

Tell us what’s happening:

Not passing any tests. Not sure what I’m missing.

Your code so far


class DisplayMessages extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
  }

  handleChange = (e) => {
    this.setState({ input: e.target.value })
  }

  submitMessage = () => {
    this.setState(prevState => ({
      input: "",
      messages: [...prevState.messages, prevState.input]
    }))
  }
  // add handleChange() and submitMessage() methods here

  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        { /* render an input, button, and ul here */ }
        <input type="text" onChange={this.handleChange} value={this.state.input} />
        <button type="button" onClick={this.submitMessage}>Add message</button>
        <ul>
         {this.state.messages.map(msg => (
           <li>{msg}</li>
         ))}
        </ul>
        { /* change code above this line */ }
      </div>
    );
  }
};

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7.

Link to the challenge:
https://learn.freecodecamp.org/front-end-libraries/react-and-redux/manage-state-locally-first

1 Like

OK, some thoughts:

I wasn’t able to get your arrow function class members to work. That looks like the right way to do it, but it looks like whatever transpiler is being used can’t handle them. AFAIK, that is a fairly new suggestion and may or may not be officially part of JS. In any case, it appears that it is not handled by the the testing rig. Just use regular functions. Maybe someone smarter than me can get them to work, but when I try to use arrow functions are class members, I get all kinds of errors in the browser console.

Of course, then you’re going to have to bind this to those functions. A popular way is to put a line like:

this.functionName = this.functionName.bind(this)

at the bottom of your constructor.

In your code:

  submitMessage = () => {
    this.setState(prevState => ({
      input: "",
      messages: [...prevState.messages, prevState.input]
    }))
  }

you are treating this.setState as if it were taking a callback. For it’s first parameter, it accepts an object, with the properties you want changed. For example, if I wanted to change the input to “gorilla”, I would:

this.setState({ input: "gorilla })

You are correct that you need to act on a copy of state and not state itself. There are two ways to do this: 1) copy state into a new object, make the changes, then give it to setState, or 2) create the new object in situ, as you send the object, like I did in the above example.

When I make those adjustments to your code, it passes.

Please let us know if I didn’t explain something well enough.

3 Likes

Thanks for the in-depth response @kevinSmith!

You’re right, it looks like this is not part of the language yet.

FWIW, this still works:

  handleChange(e) {
    this.setState(() => ({ input: e.target.value }))
  }

  submitMessage() {
    this.setState(({messages, input}) => ({ 
      input: "",
      messages: [...messages, input]
    }))
  }

Using another “form” (or type signature?) for setState that accepts a function as argument. In the docs here.
Thanks again!
Cheers

1 Like

You’re right, it looks like this is not part of the language yet.

No, but I think there are some transpilers that accept that.

Using another “form” (or type signature?) for setState that accepts a function as argument. In the docs here.

That’s a good point, I’d forgotten all about that.

thanks for the solution, it passes! however, i don;t understand why you had to set messages the way you did instead of how i supposed like so:

    this.setState({ 
      messages: [...messages, input]
    });   

1 Like

hey maybe got the answer but correct me if I am wrong
there is two problem in this code
1 . You didn’t bind the method.
2.In submitMessage method when you were setting the state of messages you were taking a argument which was not passed when button triggered so it is unknown (prevState).Instead of prevState you could use

    messages:[...this.state.messages,this.state.input],
        input:''

it would solve the problem.

That’s correct, I was using (or trying to use) the class field syntax to avoid explicit binding. This is not yet available in browsers but usually made possible by a Babel transpile step.

prevState definitely works, I posted a link to the relevant part of the docs that goes over the function form of setState, but I’ll re-post below along with the answer I arrived at.

Just to be 100% clear: my problem was resolved last June (hopefully this thread should appear as answered, specifically by the second post by Kevin above), by converting from class field syntax that I originally had (currently a stage 3 proposal which is not yet supported in browsers) to regular class methods. You can read more about the proposal here: https://github.com/tc39/proposal-class-fields. The relevant part of the React docs on how setState is being used here: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous.

One thing I noticed in your solution.

<ul>
{this.state.messages.map(message=>
(<li>{message}</li>)
)}
<ul>

This doesn’t render the message in ‘li’ element when the button is clicked. There’s one thing you missed in the syntax of that function. It does pass the test but logically it’s an error.

Hope you get my point.

That part looks fine to me, what would you change?

Try running the code. You’ll know once you do.

Here it is running in a CodeSandbox: https://codesandbox.io/s/hardcore-chandrasekhar-9k01c.

It renders for me when I click the button, not sure what you’re saying is missing (unless you’re talking about a key prop, but that’s not really needed to get the li rendering here).

This may seem to run on codesandbox.io but I saw that the map function was missing the keyword return. For me on FCC editor it wasn’t rendering the li element when I wasn’t returning something in the map function. I know not every function needs a return keyword but when I was trying to render the li without it, it wasn’t working so I thought I should share my thoughts.

This part

{this.state.messages.map(message => (
  <li>{message}</li>
))}

uses an implicit return. If you wanted to re-write it to use an explicit return, you could do

{this.state.messages.map(message => {
  return <li>{message}</li>
})}

Note the curly braces instead of parentheses. The two snippets are semantically equivalent. This is part of the language and not specific to CodeSandbox. You can read about it here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Function_body

I understand, thank you.

I think there is also something about there having to be a unique key in there.
<li key={message.toString()}>{message}</li>
This is assuming there won’t be any duplicate messages, in which case one could use map’s index value.

1 Like

True, it’s definitely best practice to use a unique key, if you don’t it can lead to bugs when removing elements or changing the order of elements. I’m not planning on changing the order of list elements anyways, so not having a key should be “safe” in this case and as you say I don’t have a meaningful unique key to assign to each element: messages could be duplicated, and I could supply the index as key but React does that as fallback anyways.

It turns out, when nothing is passed React uses the index as key because it is the best guess at the moment.
(from https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318)

Using index as key is effectively the same as not passing a key, which can bite you, as the article notes.

1 Like

Awesome. Love that they keep improving React or maybe it was a feature all along?! In any case, I’ve learned something. Thanks! :smile: