To see the app in action, check out: https://hejtful.github.io/superstar-repos/
- Clone the repository
- Change to the repository directory
- Run
yarnto install dependencies - Run
yarn startto start the dev server
Runs the app in the development mode. Open http://localhost:3000 to view it in the browser.
Launches the test runner in the interactive watch mode.
Builds the app for production to the build folder.
The "features" architecture, recommended by Redux Toolkit, was used. Since I only recognized a single feature in the requirements, the app currently only has one feature directory.
If another feature was to be added, additional feature directories would be created. For example:
- Most active engineers on GitHub in the past week -
githubfeature would be renamed togithubRepos, and the new feature would begithubUsers. ThegithubApiservice would be moved outside offeaturesand intoappdirectory, as it would be clear that GitHub API would be the main API for the app. - Most starred repos on GitLab - a new
gitlabfeature would be added, with its owngitlabApi.
Since it was a requirement to persist starred repos in localStorage, I decided to persist the whole store instead, for two main reasons:
- Time - Persisting only a specific piece of state would take more time to implement.
- Scope of the app - with such a small app, persisting the whole state didn't seem like a big problem.
I decided to add a localStorage middleware because I didn't want to add these side-effects to the reducer.
Additionally, having starred repos saved in the localStorage, and not sent to the API, made me go for displaying only the repos, from the top 25 repos, that are starred. In order to show top 25 of the starred repos, I would have to poll the API for next pages until 25 starred repos are displayed, which could end up in hundreds of requests. Additionally, when a language filter is active and less than 25 repos of that language are starred, polling would go on until all the repos are fetched.
In order to showcase working with CSS, I decided for CSS modules.
No naming conventions were used, because CSS modules are scoped by themselves, so it seemed like an overhead.
The app is responsive.
Main feature requirements are tested with integration tests in Github.spec.tsx.
Unit tests are added for localStorage middleware, date utilities, store reducer and snapshot tests for UI components.
API calls are mocked by msw library in src/test/ directory.
Since no performance issues arose (or were expected from such a small app), no optimization was done. If any performance issues were to arise, I would first consider:
- Memoizing
reposinGithub.tsxto prevent unnecessary renders when any of its dependencies change:
// Current code
const repos = isStarredFilterActive
? allRepos?.filter((repo) => starredReposIds?.includes(repo.id))
: allRepos;
// Memoized code
const repos = useMemo(() => {
if (!isStarredFilterActive) return allRepos;
return allRepos?.filter((repo) => starredReposIds?.includes(repo.id));
}, [isStarredFilterActive, allRepos, starredReposIds]);- Memoizing the star/un-star button callbacks, also to prevent unnecessary rerenders (where
repoIdwould be sent as an argument from the child component):
// Current code
onStarButtonClick={() => dispatch(starRepo(repo.id))}
// Memoized code
const handleStarButtonClick = useCallback(
(repoId) => {
dispatch(starRepo(repoId));
},
[dispatch]
);
...
onStarButtonClick={handleStarButtonClick}I would profile the app before and after implementing these optimizations, to check if it actually brought any improvement to the number and duration of renders.
With more time, I would like to have:
- Extracted
ReposList.tsxcomponent, to clean upGithub.tsxfrom render logic and styles. - Extracted
Banner.tsxcomponent, to clean upGithub.tsxfrom static HTML and styles. - Improved event handler typing in
Github.tsxand added return value types inapp/util.ts. - Used
user-eventinstead offireEventfor tests withreact-testing-library. - Used an endpoint to fetch a list of programming languages, instead of hard-coding only a small number.