React render multiple components in a row

I am building a small chat app which has the following HTML structure.

<div className="my-message">
  <div className="mess">
    <p className="my-message">Hello</p>
  </div>
  <div className="mess">
    <p className="my-message">How are you</p>
  </div>
    <p className="time-i-sent">1m ago</p>
</div>

<div className="my-message other-message">
  <div className="mess">
    <p className="my-message other-message-2">Hi</p>
  </div>
  <div className="mess">
    <p className="my-message other-message-2">Great</p>
  </div>
    <p className="time-i-sent other-message-3">5m ago</p>
</div>

You can notice that messages in a row that are from the same user are just divs with mess class. If other user responds, a new my-message div wraps the messages that the user will send consecutively. I reflect them as React components.

Here is the Messages component equivalent to my-message div that wraps consecutive messages.

const Messages = (props: any) => {
  const isOtherUser = props.userId !== props.currentUser.id;
  return (
    <div
      className={`my-message ${isOtherUser ? "other-message" : ""}`}
    >
      {props.children}
      {props.lastMessage && (
        <p className={`time-i-sent ${isOtherUser ? "other-message-3" : ""}`}>
          {getTimeSince(props.time)}
        </p>
      )}
    </div>
  );
};

And here is my Message component which should be wrapped by Messages component if they are from the same user consecutively.

const Message = (props: any) => (
  <div className="mess">
    {props.userId === props.currentUser.id ? (
      <p className="my-message">{props.text}</p>
    ) : (
      <p className="my-message other-message-2">{props.text}</p>
    )}
  </div>
);

And here is what I try to do

messages.map((message, index) => {
  if (index > 0 && messages[index - 1].userId === message.userId) {
     return (
       <Messages
         {...message}
         currentUser={currentUser}
         lastMessage={index === messages.length - 1}
       >
          <Message key={index} {...message} currentUser={currentUser} />
       </Messages>
    );
  } else {
    return (
       <Message key={index} {...message} currentUser={currentUser} />
    );
  }
})

It looks strange, but I want to check if the previous message was from the same user, just make a Message component and put it inside the current Messages component, if messages are from different users, make a new Messages component and add Message components inside it in case user sent multiple messages in a row so that in the end I have something like the following.

<Messages>
  <Message>Hi</Message>
  <Message>How are you</Message>
</Messages>
                                     <Messages>
                                       <Message>Hi</Message>
                                       <Message>Great, you?</Message>
                                     </Messages>
<Messages>
  <Message>Awesome</Message>
</Messages>
                                     <Messages>
                                       <Message>Bye</Message>
                                     </Messages>

I think this should be the logic but not sure how to achieve appending the components here I thought of HOC as well.

Answer

As you want to reduce the number of elements of messages grouping the ones with the same userId, you can try using Array.reduce:

const groups = messages.reduce((acc, message, index) => {
  if (index > 0 && messages[index - 1].userId === message.userId) {
    // Same group as before
    acc[ acc.length-1 ].push({message, index});
  } else {
    // New group
    acc.push([{message, index}])
  }
}, []);

return groups.map( (group, groupIndex) => (
       <Messages
         key={groupIndex}
         {...group[group.length-1].message}
         currentUser={currentUser}
         lastMessage={groupIndex === group.length - 1}
       >
          { group.map( ({message, index}) => (
              <Message key={index} {...message} currentUser={currentUser} />
            ))
          }
       </Messages>
  )
);

But I think maybe it would be better to modify the Messages component, so it accepts an array of messages instead of using children.

Leave a Reply

Your email address will not be published. Required fields are marked *