Project created with instructions from the MERN eCommerce course by @bradtraversy (Traversy Media). The project was not created entirely according to the instructions of the course. The difference:
- is that typscript is used on the frontend and the backend.
- linters are configured
- SCSS is used
- naming convention different
- file names differ
- the Redux setup is different
- I did not finish the course yet
Course Repo: https://github.com/bradtraversy/proshop_mern
npm install in /frontend and /backend
GraphQl not setup in the backend
npm run codegen
add .env file in /backend with the following keys
NODE_ENV=development
PORT=8000
MONGO_URI=
npm start in /frontend
npm start:dev in/backend
Typescript based WebShop written in Typescript with Redux State Management
This setup aims to remove as much boilerplate code as possible.
The redux setup consists of the following components:
- Generic Actions (custom actions possible)
- State Merger (merge of payload and state)
export interface State {
readonly name: StateName;
}
export enum StateName {
APP_STATE = 'appState',
PRODUCT_LIST_STATE = 'productListState',
}
export interface AppStateModel extends State {
[StateName.PRODUCT_LIST_STATE]: ProductListStateModel;
}
export const defaultAppState: AppStateModel = {
name: StateName.APP_STATE,
[StateName.PRODUCT_LIST_STATE]: defaultProductState,
};
export interface ProductListStateModel extends State {
products: Array<ProductDTO>;
loading: boolean;
error: string;
}Each state has a name. The names are stored in an enum. All states are part of the App state.
This creator is stored in src/state/utils. To Create an action you need 4 things:
- StateModel
- ActionType Enum
- Payload
- Name of the state or boolean
The StateModel & ActionType Enum are stored in the component directory.
The payload is type checked. It has a Partial<T> interface of given state model. With the generic creator there are no extra action definitions necessary. Define the attributes which changed in the call.
The constructor of the generic creator has 2 optional parameters. The stateName and a boolean value.
- useCustomStateMerger(boolean): When the boolean is set to true then the action loads a custom state merger which is intended to hold some business logic added by the developer. Read more about that under State Merger.
- stateName(StateName): If the useCustomStateMerger flag is false or undefined the default merger is used. The default merger needs to know to which state the payload should be applied to.
Usage
dispatch(new genericAction<ProductStateModel>(ProductListActionTypes.REQUEST_START, { loading: true }), StateName.PRODUCT_LIST_STATE, false);If the generated actions reach their limitations then self written actions can be added with the template below.
Usage
export class SomeAction extends Action {
public readonly type = SomeActionTypes.ActionName;
public reducer = (state: AppStateModel) => ({ ...state, ...payload });
constructor(public payload: Pick<SomeStateModel, 'someAttribute'>) {
super();
}
}What is this for? As the name says it merges states.
Based on the state name provided in the genericAction call the default state merger applies the payload to the correct state. That's possible because the state name equal the state attribute name in the app state model.
The generated actions include the reducer, and the action class has a flag named useCustomStateMerger. If true then the reduce function of that action gets a state merger from a map and runs the merge function. By default the payload of the action overwrites the state. But as there could also be some business logic necessary then a custom merger is needed. These are stored in src/state/state-merger/.
To create a new state merger first add a new class in the state-merger file. They look like this:
export class ProductListRequestStartStateMerger extends DefaultStateMerger {
merge(state: AppStateModel, payload: Partial<ProductListStateModel>): AppStateModel {
return {
...state,
productListState: {
...state.productListState,
loading: payload.loading ? payload.loading : defaultProductListState.loading,
error: 'State Merger working',
},
};
}
}Naming & Types
- The naming default convention is actionNameStateMerger.
- The class needs to extend the StateMerger base class.
- Implement the merge function and set the types to the StateModel
- Payload must be wrapped in
Partial<T> - The return type must be AppStateModel.
Merge
- make sure to make a copy of the app state itself and a copy of the state you want to update to avoid "Common Mistake #2: Only making a shallow copy of one level"
The class must be added to the map state-merger-mapafterwards.
[ProductListActionTypes.REQUEST_START, new ProductListRequestStartStateMerger()]Each entry is an array that consists of the ActionType Enum, and a new instance of the state merger class
A universal reducer exists for all generated actions. It can also handle the custom actions. Since the logic of the reducer is "outsourced" the original reducer is a one-liner.
export const appStateReducer = (state: AppStateModel = defaultAppState, action: Action) => universalReducer(state, action);This project was bootstrapped with Create React App.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
Note: this is a one-way operation. Once you eject, you can’t go back!
If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
You can learn more in the Create React App documentation.
To learn React, check out the React documentation.