DEV Community

Cover image for Building a messaging app with temporary messages in React and why the key prop is important
Luis
Luis

Posted on • Updated on

Building a messaging app with temporary messages in React and why the key prop is important

That is my first post in Dev.to, I hope this article help you in something.

I was working on an application chat with temporary messages, using Websockets and React, so the logic was simple, for each message from the server an element was added to an array state, then I had to iterate through it to then render a message component.

Something like

  const submitMessage = ({ value, charCode }, inputRef) => {
    if (charCode === 13 && value.trim() !== "") {
      setMessages([...messages, value.trim()]);
      inputRef.current.value = "";
    }
  };

(Sorry for the styles and I'm not using sockets in this example, because that is not related with the problem)

And nice, that is all, we end with that.

But one thing appear in my mind while I was continue coding. "What would happen if I have thousands of messages", "It probably leave thousands of render elements in the DOM and we would have to deal with an array with a large length". So, I left what I was doing and returned to this problem.

For solve this, I thought, this is easy!

First, I don't need have an array of thousands elements, I could have a fixed-length array like 5, so ... for that I just need to make a few small adjustments.

A new logic to add elements

  const submitMessage = ({ value, charCode }, inputRef) => {
    if (charCode === 13 && value.trim() !== "") {
      if (messages.length === 5) {
        // HERE WE ARE JUST PERMUTING
        const [, ...rest] = messages;
        setMessages([...rest, value.trim()]);
      } else {
        // HERE WE ARE ADDING NEW VALUES
        setMessages([...messages, value.trim()]);
      }

      inputRef.current.value = "";
    }
  };

... but the result was not as I expected :(

New messages after 5 was not appearing.
I could not understand why that was happening.
A few moments later I assumed that it was related to the fact that all the elements had already been rendered and that the new or new additions were not shown because it was occupying these positions already with a finished life cycle.

For solve that and not stay with a bad feeling.

I did something like this, in the map iteration and I kept the first submitMessage function

(<div className={classes.container} ref={ref}>
  {
    messages.map((message, idx) => {
      const indexToShow = messages.length > 20 ? messages.length - 20 : 0;
      return message.msg && idx >= indexToShow && <FadeCard key={idx} message={message.msg} />
    })
  }
</div>)

(I'm very shame)

At least, in this way I would not have to deal with thousand of elements render in the DOM.

Two weeks passed, I just could not believe that something could be simple with vanilla js and play with DOM, in React I had no idea how to do it.

So finally, after among several google searches that really did not help me, I came across a library react-toast. A toast is a component to notify events in a page, you should know what I'm saying, the functionality of this component is really similar to I'm doing with the messages. So, watching the code inside of this library, I thought "oh! that is a queue" or "oh! this is a timer to disappear the element and set the map again", but nothing of this was the answer.

After testing this ideas, I got it!

Key is the answer

I know, probably you know it too, but I ignored this because we are used to put the position of an element in array as a key in the map.

So that is the problem, the only way that react have to recognize an element in the DOM is the Key, for that reason the elements that was permuting never kept their original states.

So finally I did

  const submitMessage = ({ value, charCode }, inputRef) => {
    if (charCode === 13 && value.trim() !== "") {
      const id = uuidv4();

      inputRef.current.value = "";

      if (messages.length > 5) {
        setMessages(messages => {
          const [, ...rest] = messages;

          return [...rest, { msg: value.trim(), id }];
        });
      } else {
        setMessages([...messages, { msg: value.trim(), id }]);
      }
    }
  };
const MessageContainer = ({ messages }) => {
  const classes = useStyles();

  return (
    <div className={classes.container}>
      {messages.map(({ id, msg }) => (
        <FadeCard key={id} message={msg} />
      ))}
    </div>
  );
};

Now we can sleep calm.

Final words

  • As the documentation says, do not use the index of an element as a key if the order of these elements can change, it is very dangerous (https://reactjs.org/docs/lists-and-keys.html#keys)

  • If you have a problem and don't know how to solve it, try to think of some standard component that has a behavior similar to yours, I think that is very inspiring to find solutions.

Thank you for read.

Top comments (0)