Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project-docs/setup.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Baseline
## Baseline

Examine the JSON file located at `src/data/messages.json` to familiarize yourself with the data you will be using.

Expand Down
23 changes: 20 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import './App.css';
import ChatLog from './components/ChatLog';
import MessageData from './data/messages.json';
import { useState } from 'react';

const App = () => {
const [messages, setMessages] = useState(MessageData);

const toggleLike = (id) => {
setMessages(prevMessages =>
prevMessages.map(message =>
Comment on lines +10 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job using the callback-style for updating the state.

message.id === id
? { ...message, liked: !message.liked }
: message
)
);
};

const totalLikes = messages.filter(message => message.liked).length;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice! You could also use reduce too.


return (
<div id="App">
<header>
<h1>Application title</h1>
<h1>Messenger App</h1>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗️ On line 24, you should have changed the title so that the header rendered "Chat between Vladimir and Estragon"

Can you think of a way to dynamically get the senders' names without hardcoding "Vladimir" and "Estragon" here?

<h2>{totalLikes} ❤️s</h2>
</header>
<main>
{/* Wave 01: Render one ChatEntry component
Wave 02: Render ChatLog component */}
<ChatLog entries={messages} onToggleLike={toggleLike}></ChatLog>
</main>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/ChatEntry.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,5 @@ button {

.chat-entry.remote .entry-bubble:hover::before {
background-color: #a9f6f6;
}
}

33 changes: 23 additions & 10 deletions src/components/ChatEntry.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
import './ChatEntry.css';
import TimeStamp from './TimeStamp';
import PropTypes from 'prop-types';

const ChatEntry = (props) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destructuring props can be a matter of preference. I prefer to destructure props so that I don't need to type props. every time I want to get an attribute from the object.

Destructuring props also has some benefits:

  • you know every possible prop right away.
  • you know when a prop is being unused, in order to remove it.
  • knowing which props are being used will also remind you what you need to validate

Ultimately, you'll adapt what you do depending on what your team does.


const ChatEntry = () => {
return (
// Replace the outer tag name with a semantic element that fits our use case
<replace-with-relevant-semantic-element className="chat-entry local">
<h2 className="entry-name">Replace with name of sender</h2>
<section className="entry-bubble">
<p>Replace with body of ChatEntry</p>
<p className="entry-time">Replace with TimeStamp component</p>
<button className="like">🤍</button>
<article className="chat-entry local ">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Nitpick: white spacing is off

Suggested change
<article className="chat-entry local ">
<article className="chat-entry local">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was an optional enhancement, but how could you determine if a chat was from a 'local' sender vs a 'remote' sender so that the chat bubbles would have different styles applied to it?

If you have time to re-visit this project for React.js review, it would be good practice to try applying local vs. remote classes to the components so they render differently depending on who sent the message.

<section className="chat-entry local">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could remove this section element since you use the article element directly before this to wrap elements of the ChatEntry component.

The article element also has the exact same classes on it so it feels a little redundant to have article and then section.

Suggested change
<section className="chat-entry local">

<h2 className="entry-name">{props.sender}</h2>
<section className="entry-bubble">
<p>{props.body}</p>
<p className="entry-time">
<TimeStamp time={props.timeStamp}></TimeStamp>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work using the provided TimeStamp component here 👍

</p>
<button className="like" onClick={props.onToggleLike}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since a ChatEntry component gets liked and it knows its own id then we can pass the function down to ChatComponent and pass the id here, like this:

Suggested change
<button className="like" onClick={props.onToggleLike}>
<button className="like" onClick={() => props.onToggleLike(id)}>

{props.liked ? '❤️' : '🤍'}
</button>
</section>
</section>
</replace-with-relevant-semantic-element>
</article>
);
};

ChatEntry.propTypes = {
// Fill with correct proptypes
sender: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
timeStamp: PropTypes.string.isRequired,
liked: PropTypes.bool.isRequired,
onToggleLike: PropTypes.func.isRequired,

};

export default ChatEntry;
22 changes: 22 additions & 0 deletions src/components/ChatLog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ChatEntry from "./ChatEntry"

const ChatLog = (props) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer props to be destructured, like I mentioned in my comment in ChatEntry.

return (
<section className="chat-log">
{props.entries.map(entry => (
<ChatEntry
Comment on lines +6 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another common way you'll see components organized using map is to store the result into a variable we use in the JSX result:

  const chatEntries = entries.map((entry) => {
    return (
      <ChatEntry
        // your props
      />
    );
  });

  return <section className="chat-log">{chatEntries}</section>;

key={entry.id}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 The key attribute is important for React to be able to detect certain kinds of data changes in an efficient manner. We're also using the id for our own id prop, so it might feel redundant to pass both, but one is for our logic and one is for React internals.

sender={entry.sender}
body={entry.body}
timeStamp={entry.timeStamp}
liked={entry.liked}
onToggleLike={() => props.onToggleLike(entry.id)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should directly pass in the event handler function without needing to wrap it in an anonymous function.

Suggested change
onToggleLike={() => props.onToggleLike(entry.id)}
onToggleLike={props.onToggleLike}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be the job of the ChatEntry component to pass its own id to the onToggleLike function so that piece of data can be passed into the function in ChatEntry.jsx.

/>
))}
</section>
);
};


export default ChatLog;