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
eslint
andvscode
- 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
jest
andenzyme
- 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_template
Step 3: Init git
and follow the instructions
$ cd react_app_template/
$ git init
$ ./node_modules/.bin/eslint --init
Then 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-commit
Second, 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=src
Step 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
=development
react-scripts test
->NODE_ENV
=test
react-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.local
NOTE: 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_ENV
REACT_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 yourReact
components 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:prod
And 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.
configs
is depends onREACT_APP_ENV
, that we define as our favour.env
is depends onNODE_ENV
, that pre-defined in webpack for eachreact-scripts
command
Instruction link
Step 1: Install package
$ npm install react-router-dom
Step 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-thunk
Step 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 forapp
redux/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 reduxstore
herepromiseMiddleware.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_token
request.js
: wrapped common HTTP requests withfetch
storage.js
: manage localStorage
Step 1: install packages
$ npm i --save-dev enzyme enzyme-adapter-react-16
Step 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