A boilerplate React repository with state management, service-based custom hooks, mock service worker handlers for testing, and TypeScript
This is a boilerplate repository for a React application using TypeScript.
The intention behind this is to provide an out-of-the-box structure for many patterns that are emerging in the React world. For instance, this library uses a services pattern with a service locator pattern as well something akin to a state reducer pattern.
The goal is to have React code that is easily testable, mockable, interchangable, and readable. It is also something I want to continue to work on to provide and represent an ideal and consistent structure for React projects.
These are the things I want in a React app (or any app) insofar as I can achieve them sensibly. This started with wanting a way to easily change out functions being called by hooks e.g. login
. If I decided to switch managed services, I wanted it to be seamless. I could have just provided an interface in addition to my custom hooks actions, but then implementation details and configuration are still a problem, like in the case with firebase where the app needs to be initialized.
So with that said here are the needs:
- a provider pattern in order to easily switch out implementations when necessary.
- a way to test and mock easily.
- a place to bootstrap some third-party functionality (e.g. firebase).
- global state
- a place to connect components with services that had access to global state
- some sort of reducer + state machine pattern that made sense but was simple
with these requirements I came up with:
- a global state using useReducer, an action pattern similar to Redux/finite state machines, and Context API
- services with interfaces being provided by
react-service-container
. Since services are provided and dependencies are injected they can be easily tested. mock-service-worker
provides any server-side functionality I want to mock or test. Again, since services are provided and dependencies are injected they can leveragemsw
more easily.- custom hooks that return "actions" which can call service methods, have access to global state, can dispatch new state, and perform side effects using
useEffect
. - providers which can flexibly handle application logic for which service to provide based on environment, context, or arbitrary factors.
- TypeScript to implement interfaces, extended types (from third-party libraries), and prop types.
There are several folders to explain:
- Components
- Config
- Hooks
- Providers
- Reducers
- Services
- State
- Tests
- Types
- Utilities
All React components
Anything that is a constant or needs to read from environment variables
custom hooks that provide actions for components to use. Hooks are where you use services and also update global state/perform side effects outside of your components' activity.
Provdiders are a place to flexibly provide services. Typically, it will only provide a single type of service, but in some instances you may want to provide different types of the same service e.g. multiple cache locations or databases. Also if you decide to switch to a new type of service, this is where you'd easily swap out your existing service.
educers are the place to read actions and immutably apply new global state. There will always be a master or "root" reducer like Redux, but you can easily modularize and refactor to call functions within the root reducer.
services are primarily classes (but can be functions or Symbols) that adhere to a certain interface.
For instance, let's say you design your AuthServiceProvider
to provide a service with two methods - login
and logout
- then whatever service is provided there (for instance a FirebaseAuthService
or AuthZeroAuthService
) must of course implement login
and logout
. In other words, the Services
folder is your implmentations of the services you wish to use, while Providers
handle the business of ensuring they adhere to the contract.
the state folder has some module (I call mine store.tsx
) that provides initial state (so we know its structure), a place to configure your state contextas well as hook up your master reducer to the state context.
all unit tests and e2e tests you want to include. This also includes mocks
folder with handlers for msw (mock service worker), which provides a slick way to mock your server actions. This is particularly helpful with repos using managed services.
all of your types including service interfaces and prop types for your react components.
various utilities for you to use - e.g. calculations for business logic, interceptors for http, handlers for errors.
This repo uses Firebase out of the box, so for authentication, you have to configure firebase with the objects in config.ts
. Out of the box, it uses emulators in non-production, so this business logic can be changed.
If you change this implementation, you also need to handle the hook state for react-firebase-hooks
in auth-actions.ts
. This is used to tie the state of the authentication service (which is asynchronous and non-transparent in the case of firebase) to the global state of the application.
In addition to this, the firebase service uses emulators for testing. You can also use mocking or a sandbox service instead. I do not recommend rolling your own authentication.
This repository uses react-service-container
to provide and bootstrap services. This acts as a top-level wrapper of the React App similar to StateProvider
and MuiThemeProvider
Services need to be registered to the provider via the documentation. Simply add the service you wish to provide to the providers
prop of the ServiceContainer
in App.tsx
.