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
7 changes: 7 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,11 @@

.purple {
color: purple
}

.liked-count {
text-align: center;
font-size: 2rem;
font-weight: bold;
margin: 0.5rem 0 0;
}
22 changes: 19 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import './App.css';
import "./App.css";
import { useState } from "react";
import ChatLog from "./components/ChatLog";
import entriesData from "./data/messages.json";

const App = () => {
const [entries, setEntries] = useState(entriesData);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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


const likedCount = entries.reduce((sum, e) => sum + (e.liked ? 1 : 0), 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great work, @FluenceMind. Though you will find a number of implementations for this, the .reduce method is the one I would prefer for the given case.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Given that this function is not dependent on any piece of data that is locally scoped to our component you could opt to move its definition outside of the component and into, say, its own file.


function handleToggleLike(entryId) {
setEntries((prev) =>
prev.map((e) =>
e.id === entryId ? { ...e, liked: !e.liked } : e
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Very succinct, however I would encourage you to use a more descriptive variable name than just e. For someone looking to understand the function e doesn't readily explain what its structure and content may be—resulting in more cognitive load from the reader.

)
);
}

return (
<div id="App">
<header>
<h1>Application title</h1>
<p className="liked-count">{likedCount} ❤️s</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

An alternative could be to call the function here instead of assigning the value to a variable earlier in the code, especially since the variable is not being leveraged in any other way.

</header>

<main>
{/* Wave 01: Render one ChatEntry component
Wave 02: Render ChatLog component */}
<ChatLog entries={entries} onToggleLike={handleToggleLike} />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍🏿

</main>
</div>
);
Expand Down
40 changes: 29 additions & 11 deletions src/components/ChatEntry.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import './ChatEntry.css';
import PropTypes from "prop-types";
import TimeStamp from "./TimeStamp";
import "./ChatEntry.css";

const ChatEntry = ({ id, sender, body, timeStamp, liked = false, 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.

Once the de-structured params start getting long, you could change the formatting into the following for readability.

Suggested change
const ChatEntry = ({ id, sender, body, timeStamp, liked = false, onToggleLike }) => {
const ChatEntry = ({
id,
sender,
body,
timeStamp,
liked,
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.

Also @FluenceMind, wondering the reasoning for having like = false?

const showLikeButton =
typeof id === "number" && typeof onToggleLike === "function";
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.

@FluenceMind, also wondering the reason for having this logic for deciding if the like button will be shown based off the typing for the mentioned props?


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>
<article className="chat-entry local">
<h2 className="entry-name">{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>
<p>{body}</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change

<p className="entry-time">
<TimeStamp time={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.

</p>

{showLikeButton && (
<button className="like" onClick={() => onToggleLike(id)}>
{liked ? "❤️" : "🤍"}
Comment on lines +20 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agnostic of the reason, love see you implementing this! This is essentially how you have functionality like having a submit button appear only when the form is filled out correctly.

</button>
)}
</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,
id: PropTypes.number,
liked: PropTypes.bool,
onToggleLike: PropTypes.func,
Comment on lines +31 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great work on these proptypes, remember that they have been sunsetted in React and that will be using other type checking frameworks like Typescript.

};

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

function ChatLog({ entries, onToggleLike }) {
return (
<section className="chat-log">
{entries.map((entry) => (
<ChatEntry
key={entry.id}
id={entry.id}
sender={entry.sender}
body={entry.body}
timeStamp={entry.timeStamp}
liked={entry.liked}
onToggleLike={onToggleLike}
Comment on lines +10 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great work, especially on the formatting. To simplify your logic, you could pass in the entire message object yourself. However, you would lose out on the explicitness you have here.

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

ChatLog.propTypes = {
entries: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
sender: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
timeStamp: PropTypes.string.isRequired,
liked: PropTypes.bool.isRequired,
})
).isRequired,
onToggleLike: PropTypes.func.isRequired,
};

ChatLog.propTypes = {
entries: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
sender: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
timeStamp: PropTypes.string.isRequired,
liked: PropTypes.bool,
})
).isRequired,
};
Comment on lines +36 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You have two versions of your propTypes for Chatlog. Is this a mistake, @FluenceMind? The one defined above looks like it is the one you need, but then you override with the declaration/assignment of this one.

Suggested change
ChatLog.propTypes = {
entries: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
sender: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
timeStamp: PropTypes.string.isRequired,
liked: PropTypes.bool,
})
).isRequired,
};


export default ChatLog;