Set up your environment variables for development:
cp _develop.env .env
First you have to install dependencies and then start the project.
To install dependencies execute the following command:
$ npm install
To run the project execute the following command:
$ npm run dev
It will open a page in your default browser at http://localhost:6000.
The project is split into the following directories. When writing code, use the following guidelines to determine where it should be placed. You can also take a look at the code yourself for in-depth examples.
src/
app/
components/
constants/
hooks/
pages/
routes/
services/
state/
styles/
utils/
src/app
Redux configuration.
src/components
Components to be share for the whole application.
src/constants
Constants to be share for the whole application.
src/hooks
Here you must write your custom hooks.
src/pages
Application pages.
src/routes
Here you must import application pages and add application routes.
src/services
Http requests.
src/state
You'll find four files: [stateName]Slice.js
, sagas.js
, selectors.js
and index.js
.
For this project we use Redux Toolkit instead of redux, it is easier to configure and reduces too much boilerplate code.
Using redux toolkit you don't need to create actions, types and reducer anymore. You only need to use createSlice
function from redux-toolkit
and it'll create actions for you. Here's an example of what it looks like:
import {createSlice} from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {state.value += 1;},
decrement: state => {state.value -= 1;}
}
});
export const {increment, decrement} = counterSlice.actions;
export default counterSlice.reducer;
Then you should import the actions increment
and decrement
from our counterSlice
in your component and use it throw dispatch
. To dispatch an action you can use useDispatch
hook from react-redux
or you can use the connect
function also from react-redux
. I prefer to use connect
because is easier to test the component and it's clearer.
Internally createSlice
uses createReducer
, so you may use Immer to write "mutating" logic and it doesn't actually mutate the state. For this reason is not necessary to spread state and merge the new value. Like this:
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => ({...state, value: state.value +=1 }),
decrement: state => ({...state, value: state.value -=1 }),
}
});
If you want to create actions, you can use createAction
function from redux-toolkit
. Then you should put the action inside extraReducers
into the slice
.
import {createSlice, createAction} from '@reduxjs/toolkit';
export const increment = createAction('increment');
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
decrement: state => {state.value -= 1;}
},
extraReducers: builder => {
builder.addCase(increment, (state, action) => {
return state.value + action.payload;
})
}
});
export const {decrement} = counterSlice.actions;
export default counterSlice.reducer;
If you choose the above option you should move the actions to a new file.
src/theme
Custom theme for components and application.
src/utils
Functions to be shared for the whole application.
- 3.1 App routes usage
To create a new application route, you have to add your new route into src/routes
folder, if the new route is when the user is not logged, you have to add the route into offline
file; otherwise you have to add the route into online
file. Don't forget to add the route into src/constants/routes
to make future changes easier.
- 3.2 Api routes usage
To make a request to the API you must do this from sagas
. In development
mode you have to add your route in src/constants/apiRoutes.js
file, but for production
we don't expose the backend url, so you need to add the API route in server.js
file.
These are the core dependencies you'll need to get acquainted yourself with:
- React
- Redux Toolkit (handles state management)
- redux-saga (handles side-effects in Redux, such as API calls)
- React Router (route management)
- Redux-first-history (redux binding for React Router)
- Material ui (component library our UI is built upon)
- Formik (to make it easier to write forms with React)
- Yup (handles form validation)
- Storybook (component library)
- Jest (testing framework)
- React Testing Library (DOM interface for testing)
- EsLint (used to lint code)
Tweaking the build config requires an understanding of the following. This isn't something we'll need to tweak often:
To deploy don't forget to change the environment variables for production. Then you must run the following command:
$ npm run build
It will create a dist
folder in the root with the code bundled and minified. Then you have to serve the index.html
file located in the dist
folder running the following command in the dist
folder:
$ node server.js
If you use pm2
you should run this command
$ pm2 start server.js --name <app-name>
To run the tests you must run the following command:
$ npm run test
$ npm run test:watch # Run in watch mode
When writing tests, make sure to use the following format to keep the tests clean and consistent:
import {getByText} from "@testing-library/react";
import Button from "./Button";
describe("<Button>", () => {
let props;
const getComponent = () => render(Button, props);
beforeEach(() => {
props.children = "Label";
})
it("should render `props.children`", () => {
const {container} = getComponent();
expect(getByText(container, props.children)).toBeInTheDocument();
});
describe("when `props.plus` is `true`", () => {
beforeEach(() => {
props.plus = true;
})
it("should render a plus character", () => {
const {container} = getComponent();
expect(getByText(`+ ${props.children}`)).toBeInTheDocument();
});
});
});
Lint the code and run tests:
$ npm run validate