"bulletproof-react" is a hidden treasure of React best practices!
Tags: react, javascript, architecture, typescript
Published: 2021-11-14 12:42:03

The GitHub repository "**bulletproof-react**", which is published as an example of React application architecture, is very informative and I will share it with my own views.

https://github.com/alan2207/bulletproof-react

## Directory structure

https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md

First, you can learn about the directory structure, which tends to vary from project to project.

### Put the source code under `src`.

In **bulletproof-react**, React-related source code is stored under the `src` directory; conversely, there are no directories such as `components` or `utils` in the root directory.

For example, the default application created by [Create Next App]() has source code directories such as `pages` in the root directory, so putting them under `src` is the intentional directory structure of this repository.

The root of a real project will have a mix of markdown documentation (`docs`), CI settings such as GitHub Actions (`.github`), and Docker settings (`docker`) if the application is container-based. Therefore, if we put `components` directly at the root level, the source code of the application and the non-components will **be mixed** in the same hierarchy.

Not only is this confusing, but it is also convenient to unify the source code under `src` when writing CI settings, for example, to make it easier to specify the scope of application.

### `features` directory

An interesting point in the directory structure of this repository is the `features` directory.

It contains the following directories: 

```markdown
src
|-- assets
+-- assets # assets folder can contain all the static files such as images, fonts, etc.
*(omitted)*
+-- features # feature based modules ← here
*(omitted)*
+-- utils # shared utility functions
```

Under `features` is directories with the name of each feature that the application has. For example, for a social networking service, this might be `posts`, `comments`, `directMessages`, and so on.

```markdown
src/features/awesome-feature
|
+-- api # exported API request declarations and api hooks related to a specific feature
|
+-- components # components scoped to a specific feature
*(omitted)*
+-- index.ts # entry point for the feature, it should serve as the public API of the given feature and exports everything that should be used outside the feature
```

When deciding on a directory, it is important to consider what criteria to use. You tend to decide the directory name based on what role the module plays from the **engineer's point of view**. You may have `components`, `hooks`, `types`, etc. under `src`, and then finally create a directory for each function in each directory.

I myself create a directory called `app/Domain` for backend implementations, and then create a directory for each feature, such as `app/Domain/Auth` or `app/Domain/HogeSearch`. So it made a lot of sense to manage the front-end with the same idea.

By creating a `features` directory, you can manage components, APIs, Hooks, etc. by feature. In other words, if you have an API for each feature, you can cut the directory for the API, and if you don't, you don't have to.

Also, if you are running a service, you often want to discontinue a feature, but you **only need to delete the corresponding directory** under `features`.
 I thought this was a great idea, because there is nothing worse than having unused features lingering around like zombies.

Creating a directory for each feature will also help to speed up the verification of the business side.
If the directory is divided by `features/HOGE` as in this repository, it is possible to prioritize development speed with a fat design in the initial release, and impose strict constraints in the second and subsequent releases.

You can decide whether a file should be placed under `features` or not, based on whether it will disappear with the feature when the feature is obsolete.

You can also write ESLint rules to prohibit dependency of features -> features.

```js:.eslintrc.js
        'no-restricted-imports': [
          'error',
          {
            patterns: ['@/features/*/*'],
          },
        ],
```

https://eslint.org/docs/rules/no-restricted-imports

### Place modules that are needed across features under `src/HOGE`.

Components that are used across features, such as simple button elements, should be placed under `src/components`.

e.g. `src/components/Elements/Button/Button.tsx`

### `providers` and `routes` directories are smart.

When I'm writing React and React Native applications, I often write both Provider and Route settings in `App.tsx`, and the number of lines gets bloated, but I found it very clever that this repository has separate `providers` and `routes` directories. 

As a result, the contents of `App.tsx` are very simple. I would like to copy this.

```ts:App.tsx
import { AppProvider } from '@/providers/app';
import { AppRoutes } from '@/routes';

function App() {
  return (
    <AppProvider>
      <AppRoutes />
    </AppProvider>
  );
}

export default App;
```

#### Already support the implementation of react-router@v6 assumption.

In v6 of React Router, new features such as `<Outlet>` can be used to carve out routing into a separate object.

https://remix.run/blog/react-router-v6

https://github.com/remix-run/react-router/tree/main/examples/basic

This repository (at the time of writing, it is dependent on the beta version, so there may be minor changes in the future) already contains the following implementation examples, which I think can be used for preliminary study.

```tsx
export const protectedRoutes = [
  {
    path: '/app',
    element: <App />,
    children: [
      { path: '/discussions/*', element: <DiscussionsRoutes /> },
      { path: '/users', element: <Users /> },
      { path: '/profile', element: <Profile /> },
      { path: '/', element: <Dashboard /> },
      { path: '*', element: <Navigate to="." /> },
    ],
  },
];
```

### Supplementary information: Other examples of directory structure

I am currently managing a structure similar to the idea of the following article, rather than the idea of aggregating into `features`.

https://zenn.dev/yoshiko/articles/99f8047555f700

The `model` in this article is similar to the `features` in this repository. The general idea is to put all the `.tsx` files under `components`, which is well known from the default structure of Nuxt.js, so creating a directory `components/models` and putting components for each feature under it is also a good idea. 

## Component Design

https://github.com/alan2207/bulletproof-react/blob/master/docs/components-and-styling.md

The next section is about component design.

### Create components in-house that wrap components from external libraries.

This design pattern is called the **Anti-Corruption Pattern**. I have already worked on it myself and recommend it.

By simply using a component that wraps the `<Link>` of react-router-dom, as shown below, I can increase the possibility of limiting the scope of influence when **destructive changes are made to that component in the future**. If you are importing external libraries directly from a number of components, you will be affected, but if you have in-house modules in between, you will have a better chance of limiting the impact.

In fact, it's hard to make it work for all of them, but it's useful to keep it in mind.

```tsx
import clsx from 'clsx';
import { Link as RouterLink, LinkProps } from 'react-router-dom';

export const Link = ({ className, children, ...props }: LinkProps) => {
  return (
    <RouterLink className={clsx('text-indigo-600 hover:text-indigo-900', className)} {...props}>
      {children}
    </RouterLink>
  );
};
```

### There are many examples of implementations using the Headless component library.

Headless UI is a UI library that can be unstyled or easily overridden, and is only responsible for state retention, accessibility, etc. React components nowadays can take on all the styling, a11y, state, and communication, so a library with this kind of separation of thought is a very smart approach.

Incidentally, the same README says that for most applications, `Chakra` with `emotion` is the best choice. I also think that Chakra is currently the best component library, and `MUI` is the next best, so I rather agree with the statement :)

### A design example using react-hook-form

There is a Form library based on the premise of the heyday of Hooks called `react-hook-form` (RHF). I personally recommend it.

https://react-hook-form.com/

In this repository, RHF is embedded using a wrapper component called `FieldWrapper`. The idea is to implement a form component by putting `<input>` etc. in the `FieldWrapper`.

```tsx:FieldWrapper.tsx
import clsx from 'clsx';
import * as React from 'react';
import { FieldError } from 'react-hook-form';

type FieldWrapperProps = {
  label?: string;
  className?: string;
  children: React.ReactNode;
  error?: FieldError | undefined;
  description?: string;
};

export type FieldWrapperPassThroughProps = Omit<FieldWrapperProps, 'className' | 'children'>;

export const FieldWrapper = (props: FieldWrapperProps) => {
  const { label, className, error, children } = props;
  return (
    <div>
      <label className={clsx('block text-sm font-medium text-gray-700', className)}>
        {label}
        <div className="mt-1">{children}</div>
      </label>
      {error?.message && (
        <div role="alert" aria-label={error.message} className="text-sm font-semibold text-red-500">
          {error.message}
        </div>
      )}
    </div>
  );
};
```

I have been discussing design patterns using RHF for a long time, and have published a practical example of component design in the following article.

https://dev.to/texmeijin/component-design-idea-using-react-hook-form-v7-ie0

The design philosophy presented here was to separate the layers as View layer←Logic layer←Form layer.

On the other hand, here is a list of the relative merits of designing with wrapper components in this repository, as perceived at a quick glance.

- **The label and error display, which should be common to all form components, can be standardized**.
  - In my design, labels and error messages are handled by either the View layer or the Form layer, so they are not common. It is necessary to implement them separately.
- No need to use `useController`.
  - since registration is executed in the Form layer as `registration={register('email')}`.
    - In addition, the argument string of the register method is type-safe.
      - I'm working hard on the type definitions in `Form.tsx` to make this type-safe.
      - For example, I've adopted the design concept of wrapping View layer as HOC, but I couldn't define the type well without applying some any.
      - The use of `unknown` in the form of `extends T<unknown>` such as `TFormValues extends Record<string, unknown> = Record<string, unknown>` is a typedef tip that I often use for puzzles.
  - It may be that the number of re-renders is less than my design plan? (untested).

In addition, it satisfies all the advantages of the idea I was designing, so I thought it was completely upward compatible (great).

## Error handling

For error handling in React, `react-error-boundary` is useful.

https://github.com/bvaughn/react-error-boundary

It may be appropriate to use it in `AppProvider.tsx` as mentioned above.

```tsx
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Router>{children}</Router>
      </ErrorBoundary>.
```

I was personally impressed with the behavior of the Refresh button specified in the component for fallback.

```tsx
      <Button className="mt-4" onClick={() => window.location.assign(window.location.origin)}>
        Refresh
      </Button>.
```

What `window.location.assign(window.location.origin)` is doing here is transitioning to the top page because it is transitioning to the origin. When I saw this, I thought that I should just write `location.reload()`, but I realized that if I want to put a button on the top page, it would be more appropriate to go back to the top page, because it will keep dropping infinitely when an error occurs due to Invalid query parameter or page.

You can also use `location.href = ` to get the same behavior, but assign has the subtle advantage that it is a method call and therefore easier to write tests for, so assign is slightly preferable.

Incidentally, from a personal point of view, I thought it would be better to use `location.replace()`, which doesn't leave the error in the history, because it seems to be more subtle if you want to return to the page where the error occurred. However, I wonder if that would result in unexpected behavior.

## Other

There are many other things that I noticed, but I'll just list them here, instead of reading the Markdown under `docs` in the repository for details.

- The source code scaffolding tool is also set up.
  - With Scaffolding, you can generate files of a certain format in a target directory with a single command.
  - It is set up under the `generators` directory.
    - This is possible because the directory structure is stable.
    - https://www.npmjs.com/package/plop is used
    - By the way, I like `Scaffdog`, which can be written in markdown.
      - https://github.com/cats-oss/scaffdog
- The test code setup is also massive
  - testing-library is also via `test/test-utils.ts` as a corruption prevention layer
  - The setup of MSW is also very thorough
    - I know MSW is useful, but I hadn't imagined what it would look like after it was set up, so it's very helpful.
  - Already integrated with GitHub Actions
- Performant.
  - The basic but important point is that the page components are lazyImported in the Route file, so the code is split.
  - I was wondering why `React.lazy` can only be used for Default Export, but I heard that it can be used for named export. I didn't know that (or I never thought to do something about it).
    - https://github.com/alan2207/bulletproof-react/blob/master/src/utils/lazyImport.ts
  - I've also made it possible to record web-vitals.
- About ESLint
  - I didn't set up `import/order` because I thought it would be too radical, but now that I've seen it set up, it does seem to be easier to read...
- Type `ReactNode` is safe to use.
  - I've been using `ReactNode` for all React element props, but I was wondering if I need to be more strict since `ReactNode` can be classified into more detailed types. I was wondering if I should do that.
  - Of course, there are times when you should do that, but I'm glad to know that `ReactNode` is fine for most cases.
- Naming
  - https://github.com/kettanaito/naming-cheatsheet I've never heard of such a repository. I can use it as an internal README.
- Overall, I like the selection of libraries (this is completely subjective).
  - tailwind
  - react-hook-form
  - msw
  - testing-library
  - clsx
  - On the other hand, `react-helmet` is almost out of maintenance, and `react-helmet-async` should be better, so I published a pull request (https://github.com/alan2207/bulletproof-react/pull/45 )

---

## Summary

I've never seen a template repository with such a thorough and complete set of Production Ready configurations. Personally, I'd like to refer to it regularly as a bookmark because it contains many things I know but haven't used, such as Storybook and Cypress.

I also think [vercel/commerce](https://github.com/vercel/commerce) is a good place to learn, but **if there are any other repositories you'd recommend, please let me know**!

There are a lot of things that I haven't kept up with in the React projects I write on a regular basis at all, but I'd like to keep up with them, judging the need on a case-by-case basis.