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
Binary file modified images/react-chatlog-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 48 additions & 11 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
import './App.css';
import messagesData from './data/messages.json';
import ChatLog from './components/ChatLog';
import { useState } from 'react';

const App = () => {
return (
<div id="App">
<header>
<h1>Application title</h1>
</header>
<main>
{/* Wave 01: Render one ChatEntry component
Wave 02: Render ChatLog component */}
</main>
</div>
const toggleLikes = entry => {
return {...entry, liked: !entry.liked};
};

const countTotalLikes = messages => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A more descriptive name for this function might be getTotalLikes which would convey to others that this function returns a total count of the likes.

let total = 0;
for ( const entry of messages){
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: issues with whitespacing here

Suggested change
for ( const entry of messages){
for (const entry of messages) {

if (entry.liked) {
total += 1;
}
Comment on lines +13 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using reduce or filter to add up all the likes would make your code more concise. Something like:

const totalLikes = entries.reduce((total, chatEntry) => {
    return chatEntry.isLiked ? total + 1 : total;
  }, 0);

}
return total;
};

function App() {
const[messages, setMessages] = useState(messagesData);
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
const[messages, setMessages] = useState(messagesData);
const [messages, setMessages] = useState(messagesData);


const handleLikes = id => {
setMessages(prevMessages=>{
return prevMessages.map(entry => {
Comment on lines +24 to +25
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.

if(entry.id === 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.

You'll want to fix up code style issues while you're writing code and use a linter to check for style issues before submitting code for review.

Suggested change
if(entry.id === id){
if (entry.id === id) {

return toggleLikes(entry);
} else {
return entry;
}
});
});

};

const totalLikes = countTotalLikes(messages);
return (
<div id="App">
<header>
<h1>Chat between Vladimir and Estragon</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.

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

<h2>{`${totalLikes} ❤️s`}</h2>
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 can directly invoke countTotalLikes here instead of creating a variable first since the logic is straight forward and doesn't compromise the readability.

Suggested change
<h2>{`${totalLikes} ❤️s`}</h2>
<h2>{`${countTotalLikes(messages)} ❤️s`}</h2>

</header>
<main>
<ChatLog entries={messages}
onLikeEntry={handleLikes}
/>
</main>
</div>
);
};


export default App;

33 changes: 22 additions & 11 deletions src/components/ChatEntry.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import './ChatEntry.css';
import PropTypes from 'prop-types';
import TimeStamp from './TimeStamp';
import { DateTime } from 'luxon';

const ChatEntry = () => {
const ChatEntry = ({id,sender,body,timeStamp,onLikes, liked}) => {
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: the spacing is inconsistent here because you're missing whitespaces in several places.

Suggested change
const ChatEntry = ({id,sender,body,timeStamp,onLikes, liked}) => {
const ChatEntry = ({ id, sender, body, timeStamp, onLikes, liked }) => {

const buttonClass = liked ? 'like__item__toggle--liked' : '';
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>
</section>
</replace-with-relevant-semantic-element>
<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.

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.

<h2 className="entry-name">{sender}</h2>
<div className="entry-bubble">
<p className="entry-body">{body}</p>
<p className="entry-time"><TimeStamp time={timeStamp}/></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.

Nice work using the provided TimeStamp component 👍

<button className={`like like__item__toggle ${buttonClass}`} onClick= {() => {onLikes(id);
}}>
{liked ? '❤️' : '🤍'}
</button>
</div>
</section>
);
};

ChatEntry.propTypes = {
// Fill with correct proptypes
id:PropTypes.number.isRequired,
sender: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
timeStamp: PropTypes.string.isRequired,
liked: PropTypes.bool.isRequired,
onLikes:PropTypes.func,
};

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

const ChatLog =({entries, onLikeEntry }) => {
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: issue with whitespacing

Suggested change
const ChatLog =({entries, onLikeEntry }) => {
const ChatLog = ({ entries, onLikeEntry }) => {

const ChatEntryLogs = entries.map(entry=> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ChatEntryLogs isn't a component but a variable, so we can use standard camelCase to name it to avoid any confusion.

Suggested change
const ChatEntryLogs = entries.map(entry=> {
const chatEntryLogs = entries.map(entry=> {

return (
<ChatEntry
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.

id={entry.id}
sender= {entry.sender}
body={entry.body}
timeStamp={entry.timeStamp}
liked={entry.liked}
onLikes={onLikeEntry}

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: Remove unnecessary blank line

Suggested change

/>
);
});
return(
<div className="chat-log">{ChatEntryLogs} </div>
//rename tag
);
};

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,
onLikeEntry: PropTypes.func,

};
export default ChatLog;