This project was bootstrapped with Create React App.
Just fork this project and build your app on it.
- React App Template
- How to use
- What is included
- How we made it
- 1. Init with
create-react-app - 2. Set up
eslintandvscode - 3. Set up
pre-commit - 4. Add
LICENSE - 5. Set up NODE PATH for absolute import
- 6. Set up
dotenv - 7. Create source code structure
- 8. Set up configs for multiple environments
- 9. Set up
react-router-dom - 10. Set up
redux - 11. Add useful utils
- 12. Set up unit test with
jestandenzyme
- 1. Init with
This section is for reference, or if you want to set up your own project with some features of this template.
Instruction: Link
Pre-condition: nodejs and npm installed (LTS version)
Step 1: Navigating to a parent folder you want to place your project
$ cd ~/Documents/git/Step 2: Init app with npx (installed with npm)
$ npx create-react-app react_app_templateStep 3: Init git and follow the instructions
$ cd react_app_template/
$ git init $ ./node_modules/.bin/eslint --initThen follow the instructions:
-
? How would you like to use ESLint?
To check syntax, find problems, and enforce code style -
? What type of modules does your project use?
JavaScript modules (import/export) -
? Which framework does your project use?
React -
? Does your project use TypeScript?
No -
? Where does your code run? (Press
<space>to select,<a>to toggle all,<i>to invert selection)Browser -
? How would you like to define a style for your project?
Use a popular style guide -
? Which style guide do you want to follow?
Airbnb -
? What format do you want your config file to be in?
JSON
If Local ESLint installation not found, then choose Yes to install them locally.
If you are using VSCODE, install extension ESLint to check and fix syntax.
After set up successfully, you can enable/disable your custom rules in .eslintrc.json file:
{
// ...
"rules": {
"no-console": 0,
"import/no-named-as-default": 0,
// ...
}
}If you recieve error when using arrow funtion that is described here, add this line to .eslintrc.json
{
// ...
"parser": "babel-eslint",
// ...
}Note: ESLint rule
0- turns the rule off1- turn the rule on as a warning (doesn't affect exit code)2- turn the rule on as an error (exit code is 1 when triggered)
We've pre-configured some useful rules for the React project. Modify it as your favorite.
After set up eslint, set up vscode setting for workspace to make consistence between developers. autoFixOnSave is also useful.
In .vscode/settings.json:
{
"editor.tabSize": 2,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"eslint.autoFixOnSave": true
}To make sure your collaborative project to be clean, I also add pre-commit to run linting script everytime someone make a commit.
First, install pre-commit
$ npm install --save-dev pre-commitSecond, add config for pre-commit to package.json
{
// ...
"scripts": {
// ...
"lint": "./node_modules/.bin/eslint src"
},
// ...
"pre-commit": [
"lint"
]
}After that, before every git commit command, npm run lint will be called.
For better UI/UX, I added a scripts/lint.js script to make custom linting script, then we have to modify package.json:
{
// ...
"scripts": {
// ...
"lint": "node ./scripts/lint.js",
"fix": "FIX=1 node ./scripts/lint.js"
}
// ...
}Have a look at this file, you can see that all I did is handling result from eslint manually and print custom info.
Yes, we're in the open source world. Remember to choose your right LICENSE here, but please keep my LICENSE as a new name LICENSE.namdaoduy if you use this template!
// Ewww
import { Header } from './../../../../../components/Common/Header';
// Yasss
import { Header } from 'components/Common/Header';This is not magic. We just need 3 step to set up absolute import for our app:
Step 1: Add NODE_PATH to .env file
# file: .env
NODE_PATH=srcStep 2: For eslint, add this to .eslintrc.json
{
// ...
"settings": {
"import/resolver": {
"node": {
"paths": ["src"]
}
}
}
}Step 2: Create file jsconfig.json so VSCODE can enable absolute import intelliSense
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./src/"
},
"exclude": [
"node_modules"
]
}You're all set! Now you can use absolute import like a pro!
Wait a second.
create-react-app has already included dotenv. If you eject a CRA project and look into scripts/env.js, you can see pre-configured dotenv.
Some env files we can use are listed here
We can EJECT the CRA app to config every environments we want. But in this template, I want to make a clean template with create-react-app, means NO EJECT. So, if you want to configure multiple .env files for your custom environments, you have to eject CRA and config by your own.
IMPORTANT!
NODE_ENV can not be overrided. It depends on the script you run:
react-scripts start->NODE_ENV=developmentreact-scripts test->NODE_ENV=testreact-scripts build->NODE_ENV=production
So, be careful when use these .env files. We'll have configs for multi environments later, so keep in mind that, we mainly use .env and .env.local for set up some common environment variables.
Step 1: Add .env files
.env
.env.local-example
.env.development
.env.development.local-example
.env.production
.env.production.local-example
Step 2: In .gitignore, add these lines
# local files
.env.local
.env.development.local
.env.production.localNOTE: As this link, .env files end with .local postfix should be ignored from git, because those files are for local development, and it will overwrite the same .env file without .local postfix.
So with these files, we'll add -example postfix to it, and this file will not be ignored in .gitignore. Developer will copy this example file and remove -example postfix to use for local development.
Step 3: Add some env variables to .env files
In .env.production, add
GENERATE_SOURCEMAP=false
This one will make sure the build not include source map, that expose your React codes.
NOTE: React App can only read these env variables:
NODE_ENVREACT_APP_+ anything
I added some folders and move files in src to create a structure
src
├── assets
│ ├── css
│ │ ├── App.css
│ │ └── index.css
│ └── images
│ └── logo.svg
├── components
│ ├── __tests__
│ │ └── App.test.js
│ └── App.js
├── configs
├── constants
├── redux
│ ├── actions
│ ├── reducers
│ └── store
├── utils
│ └── serviceWorker.js
└── index.js
About each directory
assets: contains images and css (assets stuff)components: all yourReactcomponents codes. You can create sub-directory for nested componentsconfigs: your configurations, I'll set up later for multiple environmentsconstants: contains const, enum, ...redux: all aboutredux, we'll set up laterutils: contains all helpers, utilities modulesindex.js: our main js file__tests__: when we write unit test for a component, create__tests__folder in the same directory and put test file in there.
Step 1: set up config files
I added some files here in configs folder
src
└── configs
├── base.js
├── dev.js
├── index.js
├── local.js
└── prod.js
As we can not override NODE_ENV for our script, so we'll use another variable, REACT_APP_ENV to set up configs.
So we'll have 3 files for 3 env local, development, production. Look inside each file:
base.js: here we have all configs that will apply to all environments
const baseConfig = {
appName: 'react_app_template',
};
export default baseConfig;local.js: configs for local environment
const localConfig = {
apiUrl: 'http://127.0.0.1:8000',
};
export default localConfig;dev.js: configs for development environment
const devConfig = {
apiUrl: 'http://api-dev.example.com',
};
export default devConfig;Same for prod.js. Then we'll combine them all in index.js
import deepFreeze from 'deep-freeze';
import baseConfig from './base';
import localConfig from './local';
import devConfig from './dev';
import prodConfig from './prod';
const env = process.env.REACT_APP_ENV;
let envConfig = {};
if (env === 'development') {
envConfig = devConfig;
} else if (env === 'production') {
envConfig = prodConfig;
} else {
envConfig = localConfig;
}
const configs = {
...baseConfig,
...envConfig,
};
deepFreeze(configs);
export default configs;Depends on which env is defined in REACT_APP_ENV, we'll export the right configs for that env. If not provided, localConfig will be used as default.
Here I also installed deep-freeze to make configs object immmutable.
Step 2: modify scripts in package.json
{
// ...
"scripts": {
"start": "REACT_APP_ENV=local react-scripts start",
"start:dev": "REACT_APP_ENV=development react-scripts start",
"start:prod": "REACT_APP_ENV=production react-scripts start",
"build": "REACT_APP_ENV=local react-scripts build",
"build:dev": "REACT_APP_ENV=development react-scripts build",
"build:prod": "REACT_APP_ENV=production react-scripts build",
"test": "REACT_APP_ENV=local react-scripts test",
"lint": "node ./scripts/lint.js",
"fix": "FIX=1 node ./scripts/lint.js"
},
// ...
}Now, if you want to use different environment, just run
# local
npm start
npm run build
# dev
npm run start:dev
npm run build:dev
# prod
npm run start:prod
npm run build:prodAnd then in your modules
import configs from 'configs';
console.log(configs.apiUrl);That's it!
NOTE: environments in configs and .env are NOT the same.
configsis depends onREACT_APP_ENV, that we define as our favour.envis depends onNODE_ENV, that pre-defined in webpack for eachreact-scriptscommand
Instruction link
Step 1: Install package
$ npm install react-router-domStep 2: Add root BrowserRouter and Switch in App.js
See the codes
Here comes the magic of best practice structure: react with redux
Our structure will look like this after setting up
src
├── constants
│ └── actions.js
└── redux
├── actions
│ └── app.action.js
├── reducers
│ ├── app.reducer.js
│ └── root.reducer.js
└── store
├── index.js
└── promiseMiddleware.js
Step 1: install some packages
$ npm install redux react-redux redux-thunkStep 2: create constant for some actions
We'll add a new file in constants/actions.js contains some test actions
Step 3: create some action creators
We'll add a new file in redux/actions/app.action.js contains action creators of app
Step 4: create reducers
We'll add 2 files
redux/reducers/app.reducer.js: reducer forappredux/reducers/root.reducer.js: combine all reducers here
Step 5: create store and middleware
In redux/store, we'll have 2 things
index.js: config and export reduxstoreherepromiseMiddleware.js: a custom promise middleware for redux. This one is based on how my colleagues at Got It implemented, and I think this one is the best practice so far.
About middleware, we also use redux-thunk (this one is very simple, but I'll use npm package).
If you want to learn more about how redux middleware work, see this article.
Step 6: set up Provider in index.js
// ...
import { Provider as ReduxProvider } from 'react-redux';
import store from 'redux/store';
// ...
ReactDOM.render(
<ReduxProvider store={store}>
<App />
</ReduxProvider>,
document.getElementById('root'),
);
// ...Step 7: Usage
// ...
import { connect } from 'react-redux';
import { testPromiseSuccess } from 'redux/actions/app.action';
export class ExampleComponent extends React.Component {
logIn = () => {
const { testPromiseSuccess } = this.props;
testPromiseSuccess();
}
display = () => {
const { loggedIn } = this.props;
return loggedIn ? 'Yes' : 'No';
}
// ...
}
const mapStateToProps = ({ app }) => ({
loggedIn: app.check,
});
const mapDispatchToProps = {
testPromiseSuccess,
};
export default connect(mapStateToProps, mapDispatchToProps)(ExampleComponent);That's all!
I've added some useful utils that you could use:
auth.js: manageaccess_tokenrequest.js: wrapped common HTTP requests withfetchstorage.js: manage localStorage
Step 1: install packages
$ npm i --save-dev enzyme enzyme-adapter-react-16Step 2: Add set up file in src/setupTests.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });Step 3: Add jest config in package.json to collect coverage
{
// ...
"scripts": {
// ...
"test:cov": "REACT_APP_ENV=local react-scripts test --watchAll=false --coverage"
},
// ...
"jest": {
"collectCoverageFrom": [
"<rootDir>/src/**/*.{js,jsx}",
// ignore files you do not want to collect coverage
"!<rootDir>/src/utils/serviceWorker.js"
]
}
}Step 4: write test :D