https://stock-chart-omega.vercel.app/
npm start
npm run storybook
npm run test
or
npm run test:ui
stock-chart/
├── .storybook/
├── public/
├── screenshots/
├── src/ #
│ ├── components/ # feature specific components, often contains business logic
│ ├── api/ # api client.File name corresponds to api documentation, e.g Tickers=>Tickers.ts,
│ ├── lib/ # shared code used by different features, need to be generic, can't import from features
│ ├── layouts/ # layout components are containers that arrange/group components in some pattern
│ ├── mocks/ # msw handlers and server
│ ├── providers/ # context providers
│ ├── testing/ # testing utilities
│ ├── utils/
│ ├── index.tsx
├── components.json # used by ShadCn
PS: if app grows we can add a 'features' folder, right now 'features' are organized within xxxSection folder naming convention to keep it simple and easy to browse through components
- Caching
The initial implementation has an issue, that is, after user have made a selection , close the dropdown, and open it again, the dropdown items disappears because React-Select library considers user input as empty when user opened the dropdown again.
This issue is fixed by manually caching fetched data and set it to defaultOptions such that the next time user opened the dropdown, they will see previous search results as 'default options'
- Debounce The lack of debounce is intentional. This is because with debounce user will not be able to see the 'narrowing' process. That is, instead of seeing the search results of "a", and then "ab" and then "abc", user will experience a lag and see search result of "abc".
- if user choose a from date greater than end date, end date will be changed to from date.
- similarly, if end date is earlier than start date, start date will be set to end date.
- future dates are disabled
- useQueries https://tanstack.com/query/v4/docs/framework/react/reference/useQueries is used to fetch stocks price data in parallel.
- when user add a new stock e.g now we have Apple, and added Google, a new api call will be made to fetch Google data, but no api call will be made to fetch Apple data since Tanstack Query already have cached result for Apple, unless parameters changed (date, price type etc)
- user can click on retry if there is an error
- dark mode has been implemented
- efforts have been made to tweak color theme to have sufficient contrast and with the help of Storybook's a11y add-on Before After A11y colors
- msw is setup to mock http request/response for tests, e.g. stock-chart/src/mocks
- msw is linked to storybook to provide mocked response
- all components are created with being testable in mind, for example stock-chart/src/providers/ReactQueryProvider.tsx is reusable in tests to allow overriding the app queryClient
- errors are handled as much as possible at component level by rending simple error messages
- error boundary has been setup to catch application wide errors
- stats.html is generated at build time to analyze bundle size (rollup-bundle-visulizer-plugin)
- large libraries are manually split so that each bundle have size small than 500kb, assuming that http2 is enabled this will speed up FCP.
- why not vendor chunk ?
- this PR discussion when vite removes vendor chunk by default vitejs/vite#6534 explains it
- TLTR:
With ever more improved modern tree-shaking and code splitting algorithms, vendor modules may not "change less often". When you (un-)imported new methods from vendor modules, the chance of changing the output vendor chunk is actually much higher than they used to be when these algorithms didn't exist. Also the vendor chunk implementation needs to be always inline with the bundler's code splitting algorithms, or you'll have a larger vendor chunk with unnecessary codes in it.
- Since Highchart library is relatively large, it is lazily loaded until it is needed. This is done primarily to demonstrate since there is no actual slow loading issue and code splitting is commonly done at route level.
- keyboard navigations (yes)
- colors (yes)
- screen-reader friendly (partial)
- index.html have
<noscript>
tag added - types and utils are co-located by default and extracted into separate file only if
- a single file is too long and becoming unreadable,
- the type/util is shared/duplicated 3 times (following duplicate twice rule)
- The following react-query options are set considering api rate limit:
refetchOnMount: false,
refetchOnWindowFocus: false,
retryOnMount: false,
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
retryDelay: (attemptIndex: number) =>
Math.min(30 * 1000 * 2 ** attemptIndex, 2 * 60 * 1000), // try at 30s first, then 60s, then 120s
})),