Web application for browsing, sorting, and filtering Pokémon.
npm installto install dependencies.npm run devto start the development servernpm run prettierto check code formattingnpm run prettier:fixto fix formatting
npm run lintto check for linting errorsnpm run lint:fixto fix auto fixable linting errors
npm testto run all testsnpm run buildto build the project- it will be built in the
distfolder
- it will be built in the
For the design we chose to have a list of Pokémon to the left of the screen, and a sorting and filter section to the right, as well as showing detailed information about one Pokémon in the middle of the screen. For smaller screens both the Pokémon-list and the sorting/filter section collapses into a button to make for a better user experience.
We chose to wrap our application (in App.tsx) in our ContextProvider, such that we have access to some global state in the application. The main purpose of the context is to keep track of which Pokémon are currently shown, as well as the current filters and sorting method. If we did did not use the context, the states would have to be stored and sent down the component hierarchy from App.tsx, since our components that need to know about the same state are not directly related. This is something we did not want, as App.tsx would become cluttered, and we think our solution results in easier code to read and maintain.
As per not querying the API unnecessarily, we use TanStack Query and its useQuery/useInfiniteQuery hook. We know that our API is static, so we use a query client with infinite staleTime and gcTime to reduce our number of calls. For some API calls, we "optimize" even more, as some of our query functions call other functions for individual Pokémon. See getPokemonById which is called by getPokemonSliceAllData (both in Requests.ts), which is called by a query function. In this function (getPokemonById), we manually check the cache for the Pokémon, and if it is there, we instantly return the cached data. This way our application will never request the same data from the API twice in the same session. We think this is the best way to handle our situation since our API does not support fetching detailed information about multiple Pokémon at once.
We have decided only to fetch 20 Pokémon when the website loads. Then the user can load more if they want to. By doing this, we reduce the number of API calls even more, but at the same time let the user decide if they want to see even more Pokémon.
Our filtering of Pokémon might not be as intuitive as we would want it to be, but there is a reason. Now, our filters are union-based, meaning that if you select "Fire" and "Water", you will get Pokémon that are either Fire or Water (not necessarily both). The reason we do not have intersection-based filters is that since we are fetching 20 Pokémon at a time, we would have to fetch a lot of Pokémon to get almost any results with more than one filter. We think that this is a good trade-off, as we do not want to make too many API calls.
We use sessionStorage to store the user's currently selected Pokémon, filters, and sorting methods. This is done to make the user experience better, as the user keeps their state in the application even if they refresh or close and reopen the page (in one session). We do not use localStorage for that since we do not want to keep this state between sessions.
We use localStorage to store the user's favorite Pokémon, such that they are saved between sessions.
We are testing most of the components of the project. We also test the utility functions in utils.ts.
How/what we are testing:
- our own components
- including props, state, and user interaction
- use mocking so that we do not depend on the API during testing
- use snapshot testing to ensure that the components are rendered as expected
We have also done manual testing to ensure that the application works as expected. Quite a lot of time has been spent on testing the application on different browsers and devices with different screen sizes.
