A Modern React App to Ring in 2021
A million tiny c̶u̶t̶s̶ decisions
Decisions, decisions
Building a browser app in React can be intimidating. Developers must make many decisions including (my choices are in parens):
- React Native? (Not now, maybe later?)
- Typescript? (Yes!)
- Look and feel (Material-UI 5, still in alpha)
- Components (FCs and hooks)
- External data access and caching (React Query 3)
- State management (Recoil, still in prerelease)
- Page flow (React Router)
- Feedback (React Suspense, still in prerelease)
- Text translation (i18next)
I started this project in November 2020 and subsequently r̶e̶w̶r̶o̶t̶e̶ refactored it w̶a̶y̶ ̶t̶o̶o̶ ̶m̶a̶n̶y̶ several times into something that I was satisfied with. It doesn’t do much: you can change the language, log in, see some data from a REST endpoint (even using a local proxy to work around CORS blocking), and log out. However, this foundation is ready to support an unlimited amount of functionality.
But change is constant. Server components will alter the React landscape again (see video). Yay! More decisions!
You’re standing in front of a white mailbox. You may:
- Learn how this project was created
- Run it on codesandbox.io
- Run it locally (assuming you already have nvm and git installed) via:
- git clone https://github.com/dev-guy/react-spa.git
- cd react-spa
- nvm i
- npm run start
The app looks something like this:
Redux, MobX, React Context, and Recoil
For global state management, Redux was overkill so I tried to use MobX for about a day and replaced it with React Context after I decided that annotations were not worth the effort. But after spending time on a Typescriot friendly React Context prototype, I replaced it with Recoil because of its elegance and abundant documentation. I think of Recoil as a port of MobX specifically for React. Given this app’s humble needs for global state, Recoil is only marginally better than React Context (which comes out of the box) but I’m not turning back. I think it will help with more challenging requirements.
After logging in (Username: a, Password: a), an avatar icon will appear in the upper-right hand corner.
You can hover over it and select ‘logout.’ The login page will immediately appear. Thank you Recoil for this easy to use magic!
The application’s global state is stored in localStorage. This is needed to handle refresh, external links (aka deep linking), and users manually changing the browser URL. Here’s an example of custom serialization/deserialization (aka serde) in Typescript. I tried a few serde frameworks that were overkill for my needs. Obviously, you’ll want to write your own. For example, the username and password would usually be replaced by tokens.
Typescript and REST Responses
Typescript is great but it can’t enforce correctness of external data, such as REST service responses. What’s the best way to map REST responses to the rigid world of Typescript?
- Define your own data structures (in Typescript) that describe REST responses.
- Leverage third-part packages that map REST responses to Typescript objects without writing tedious code. I decided to use ts.data.json.
- Define application-level interfaces that represent the data that is returned by React Query. This way, your components will be decoupled from REST interfaces and you can move data transformations into a reusable and more maintainable library.
See the example.
Although the JsonDecoder definitions look redundant when compared to their corresponding Typescript interfaces, there isn’t a 1–1 correspondence between REST responses and application interfaces. So this extra layer of definition ended up being better than hand-crafting tedious conversion logic.
Ejecting the React Query Cache
The QueryClient instance provided to <QueryClientProvider> should be discarded when the user logs out; otherwise, there’s the possibility for different users to see each others’ data. I therefore added a QueryClient object to the global state managed by Recoil. See the example. This approach is foolproof and is easier than clearing the cache.
There’s always more to do
More basic feature examples are needed, such as:
- Styling and theming, such as a dark mode switch
- User-friendly React Query error handling
- Warning inactive users that they are about to be logged out
- Prompting users to switch to a new version
I will add more features over time. If you want to contribute, please fork the project and submit pull requests.
Happy 2021!
There exists an ever-growing abundance of free information about the React ecosystem. However, it’s challenging to find one project with enough depth to help beginners traverse a dense technology forest. This barebones application is my gift to you on the first day of the new year. May you be well and your endeavors prosperous.