Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React-coding-challenges #1

Draft
wants to merge 17 commits into
base: baseline
Choose a base branch
from
Draft

React-coding-challenges #1

wants to merge 17 commits into from

Conversation

superopengl
Copy link
Owner

@superopengl superopengl commented Jun 14, 2023

Finished two hard level challenges.

Chatter (Hard) ✅

I mainly did below changes.

  • Added WebSocket event listeners and removing them when unmount.
  • Upgraded old buggy dependency use-sound
  • Refactored or created below components for re-render optimisation
    • Footer
    • Message
    • Messages (separate chat thread part out to MessagePanelList)
    • User and UserList
  • Changed LatestMessagesContext to use useRef and Observable instead of useState to avoid unnecessary descendant components from being re-rendered when state changes.

Spotify (Hard) ✅

I mainly did below changes.

  • Read REACT_APP_SPOTIFY_CLIENT_ID and REACT_APP_SPOTIFY_CLIENT_SECRET from frontend env vars, according to Spotify API doc and .env.local (git-ignored). This is an unsafe way, but adding in frontend is just for completing the challenge. In production design, they shouldn't be disclosed in frontend and should be handled by backend.
  • Implemented the way of getting Spotify API access token, according to API doc https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow.
  • Added a singleton mechanism of getting the API access token, which can make sure no extra token request can be made until it's expired.
  • Implemented client API calls to Spotify for new releases, playlists, and categories, as required.
  • Added simple jest unit tests for the API calls. (Further refactoring needs to speed up testing if it's production code)
  • Use rxjs instead of native Promise for showing off purpose. Specifically for this challenge, rxjs seems overkilling. Promise.all and sequencial awaits can work as well.

Notes:

  • Didn't do linting, as different team may have different preferences.
  • Didn't do import/export formalisation (I know some teams prefer default export and some teams don't)
  • Didn't do further code refactoring to make it more test friendly, as it may add more noise in code review. Considering this is an interview challenge.
  • Tried to maintain the existing code as it is as possible, to avoid adding noise for code review. (For example, some code are not "one-func/component-per-file". If production code, they need to be refactored and restructured)

@superopengl superopengl marked this pull request as draft June 14, 2023 11:05
"socket.io-client": "^3.1.3",
"use-sound": "^2.0.1",
"use-sound": "^4.0.1",
Copy link
Owner Author

@superopengl superopengl Jun 14, 2023

Choose a reason for hiding this comment

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

Upgraded use-sound as the 2.0.1 was buggy (joshwcomeau/use-sound#42) and causes unwanted re-rendering.

Before, every playSend and playReceive causes the component to be re-rendered, which isn't necessary.

}

const onChangeMessage = (e) => {
setCanSend(!!e.target.value);
Copy link
Owner Author

Choose a reason for hiding this comment

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

Refactored this Footer component so that when the chat thread updates, this component won't be re-rendered.


export default function Message({ nextMessage, message, botTyping }) {
const Message = React.memo(({ message, isLast }) => {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Refactored this Message component to avoid unnecessary re-rendering when botTyping changes.

import { MY_USER_ID } from '../../UserList/constants/users';

const BOTTY_USER_ID = 'bot';

Copy link
Owner Author

Choose a reason for hiding this comment

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

Refactored and moved the message list (chat thread) component into a separate component, so as to separate the Socket operations from message list rendering. Otherwise, the Socket event listeners will be frequently registered/removed on message list changes (new message added or botTyping changes)

const isLast = (!nextMessage && (!botTyping || message.user === MY_USER_ID))
|| (nextMessage && nextMessage.user !== message.user);
return !!isLast;
}
Copy link
Owner Author

Choose a reason for hiding this comment

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

Moved the isLastMessage check out of the Message component to avoid the Message component from being re-rendered whenever botTyping changes.

})

return () => sub$.unsubscribe();
}, []);
Copy link
Owner Author

Choose a reason for hiding this comment

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

Above change can make sure only the user Bot's latest message gets re-rendered. Before, all users got re-rendered whenever new bot message came in.


// Publish global latest messages change
subject.next({ ...messages, [userId]: value });
}, []);
Copy link
Owner Author

Choose a reason for hiding this comment

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

Using useRef & Observable instead of previous useState can avoid unnecessary child component re-rendering. Furthermore, avoid unnecessary Socket listener registers/deletions.


const BASE_URL = `https://api.spotify.com/v1/browse`;
const CLIENT_ID = process.env.REACT_APP_SPOTIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.REACT_APP_SPOTIFY_CLIENT_SECRET;
Copy link
Owner Author

Choose a reason for hiding this comment

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

Saving CLIENT_SECRET in frontend is unsafe and should be moved to backend. However, I just keep it in frontend as the README guided. And considering this is an interview coding challenge, I skipped implementing a "real" backend authentication solution.

'Content-Type': 'application/x-www-form-urlencoded',
}
return httpPost$('https://accounts.spotify.com/api/token', body, headers);
}
Copy link
Owner Author

Choose a reason for hiding this comment

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

finalize(() => authTokenSource$ = null),
share()
)
}
Copy link
Owner Author

Choose a reason for hiding this comment

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

Above is a singleton design that can make sure concurrent Spotify requests can maximumly trigger only one request to get the access token.

React.useEffect(() => {
const sub$ = getCategories$().subscribe(setCategories);
return () => sub$.unsubscribe();
}, [])
Copy link
Owner Author

@superopengl superopengl Jun 14, 2023

Choose a reason for hiding this comment

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

Trigger three APIs in three useEffect calls so that they won't interfere with each other. And they will be triggered as quickly upon the React runtime and browser's resource (like concurrent request limit per host). In reality, they might be in parallel or not.

'messages__message--last': (!nextMessage && (!botTyping || message.user === ME))
|| (nextMessage && nextMessage.user !== message.user)
'messages__message--me': message.user === MY_USER_ID,
'messages__message--last': isLast
Copy link
Owner Author

Choose a reason for hiding this comment

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

Before, passing botTyping in will cause all Messages being re-rendered when bot is typing, which isn't necessary. After change, only the last one Message is re-rendered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant