- React
- Create React App
- swr
- Recoil
- Formik
- react-typesafe-routes
- react-error-boundary
- i18next
- Notistack
- Storybook
- Typescript
Follow the Description to get started with this Project.
- Install Dependencies
npm install
- Start Dev Server
npm start
- Run Storybook
npm run storybook
- Explore existing components
- Run all tests
npm test
The project uses a folder structure grouped by features.
The top-level folders are public
, docs
and the source folder src
. The source folder consists of common
and features
.
The common
folder contains global configuration and should only be used sparingly. Most development should happen in the features
directory.
"The definition of a “feature” is not universal, and it is up to you to choose the granularity. If you can’t come up with a list of top-level folders (features), you can ask the users of your product what major parts it consists of, and use their mental model as a blueprint." [1].
.
├── public # Ressource folder
├── docs # Documentation files
├── src # Source files
│ ├── common # Global configuration files
│ ├── pages # Global pages folder for all pages
│ ├── features # Contains all the components, styles, models, ... grouped by features
│ │ ├── app # App wide components, state, models
│ │ ├── authentication # Everything related to the authentication feature (login, currentUser, ...)
│ │ │ ├── components # All components related to authentication
│ │ │ ├── apis # All apis related to authentication
│ │ │ ├── services # All servcies related to authentication
│ │ │ ├── models # All models related to authentication
│ │ ├── todo # A demo feature
│ │ │ ├── components # All components related to todos
│ │ │ ├── apis # All apis related to todos
│ │ │ ├── services # All servcies related to todos
│ │ │ ├── models # All models related to todos
│ ├── stories # All storybook stories
│ │ ├── components # Stories for single components
│ │ ├── composites # Stories for composed components
│ │ ├── pages # Stories for pages
│ │ ├── mocks # Mock components (see section "Mocking in Storybook")
├── ...
└── README.md
The objective of services is to organize and share business logic. React components and services are separated to improve testability and maintainability.
Each service should have exactly one purpose. Do not create large services, instead prefer many small services.
- Bad: todoService
- Good: filterTodoService, todoReportService, exportTodoService, ...
Services are implemented as modules. Jest is used to mock services in tests.
There are two categories of components. Presentational and View-Container components.
Presentational components are ordinary react components but they should not access external state. All state in presentiational components must be self contained or passed in from View-Containers through props. Presentational components should be named without suffix.
Write stories for major presentational components
View-Containers are stateful react components without any DOM markup and styles. They are providers of data and behaviour to presentational components.
Only View-Containers or services should access external state like data fetching or recoil atoms.
View-Containers should be named {name}ViewContainer
.
Do not write stories for View-Container components
Simple:
const StyledDiv = styled('div')({
textAlign: center,
});
With theme:
const StyledDiv = styled('div')(({ theme }) => ({
padding: theme.spacing(1),
}));
Presentational components should work without mocking in Storybook. However, if a presentational "pure" component embeds a View-Container component then mocking is required.
There is no test environment available in Storybook, therefore Jest cannot be used as a mocking library.
There are two alternate ways to mock a component:
- Using MockUtil (preferred):
Open the View-Container component you want to mock. Make sure the component is default exported using the withApplyMockInStorybook
higher order component.
export default withApplyMockInStorybook(MyComponentViewContainer);
Now each time this component is loaded in Storybook an placeholder appears.
The placeholder can be replaced with an custom mock implementation.
For this purpose, create a new file {ComponentName}Mock.tsx
in src\stories\mocks
.
Content of mock file (example):
import { mockComponent } from '../../features/app/components/MockUtil';
mockComponent(MyComponentViewContainer, (props) => {
return <MyComponent someProp="value" anotherProp="..." />;
});
Open src\stories\mocks\index.tsx
and import your mock file. Restart Storybook. The mock component should be used now.
- Using webpack aliases:
This method involes changing the Storybook webpack configuration. It should only be done in exceptional cases, prefer method (1).
Open .storybook\main.ts
and navigate to the "alias" section of the webpack configuration. Add a new alias with the exact import path.
Place the mock in src\stories\mocks
.
const { enqueueSnackbar } = useSnackbar();
Success:
enqueueSnackbar('message', { variant: 'success' });
Error:
enqueueSnackbar('message', { variant: 'error' });
Exceptions during rendering of pages are automatically catched by an error boundary. An Error Fallback Page is shown. However, error boundaries do not work in event handlers (onClick, ...). Therefore the following pattern should be used in event handlers:
import { useErrorHandler } from 'react-error-boundary';
...
const handleError = useErrorHandler();
...
try {
// do something
} catch (err) {
if (err instanceof MyError) {
// ... handle expected errors ...
} else {
// forward unknown errors to react-error-boundary
handleError(err);
}
}
Translations are provided using i18next.
The locale files are located in public\locales
.