RNBoilerplate
React Native boilerplate (Typescript + react-navigation + redux + redux-thunk)
ESLint and Prettier
- add .eslintrc.js
module.exports = {
root: true,
env: {
es6: true
},
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true
},
rules: {
'prettier/prettier': 'error',
quotes: ['error', 'single'],
indent: ['error', 4],
semi: [1, 'always']
}
},
parser: 'babel-eslint',
extends: [
'@react-native-community',
'prettier',
'prettier/@typescript-eslint',
'prettier/react'
]
};
- add .prettierrc
{
"printWidth": 100,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid",
"proseWrap": "never",
"tabWidth": 4,P
"jsxBracketSameLine": true
}
- install ESLint and Prettier
yarn add babel-eslint
yarn add -D eslint prettier @react-native-community/eslint-config eslint-config-prettier eslint-config-typescript
- install ESLint extention on Vistual Studio Code
- update Vistual Studio Code settings
Type ⌘
+ ⇧
+ p
to open settings.json
"eslint.validate": [
{
"language": "javascript",
"autoFix": true
},
{
"language": "html",
"autoFix": true
},
{
"language": "vue",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
}
],
"eslint.enable": true,
"eslint.alwaysShowStatus": true,
"eslint.autoFixOnSave": true,
"prettier.singleQuote": true,
"prettier.arrowParens": "always",
"prettier.tabWidth": 4,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
- Restart Vistual Studio Code
Typescript
- add .tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["es6"],
"moduleResolution": "node",
"noEmit": true,
"strict": true,
"target": "esnext",
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@": ["src/*"],
"tests": ["tests/*"]
}
},
"exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
}
- install dependencies
yarn add typescript @types/jest @types/react @types/react-native @types/react-test-renderer
- eslint and prettier settings
- install dev dependency:
yarn add -D eslint-config-typescript
- add typescript config in eslintrc.js
- install dev dependency:
module.exports = {
extends: [
'prettier/@typescript-eslint'
]
};
- rename
.js
files to.ts
files. DO NOT change index.js (where your AppRegistry is called) or circleci may build failed - rename files with JSX elements to
.tsx
files
Redux
Prerequisites
- install react-native-debugger
- apply the extension in your project
const store = createStore(
appReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
If you are using redux middleware (e.g. redux thunk or redux saga), your extension settings would be
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(appReducers, composeEnhancer(applyMiddleware(ReduxThunk)));
- open debugger
open "rndebugger://set-debugger-loc?host=localhost&port=8081"
This port is equal to metro bundler port
- Switch to dark theme and restart the debugger
Enable Dark Theme In Chrome DevTools
- Enable debug on your app
⌘
+ D
=> click debug
- Install redux dependencies
yarn add @types/react-redux react-redux redux
Fundamentals
- Actions
What happened
Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch()
.
You will have one actionTypes file and many action files in the actions directory:
ActionTypes.ts: all types of actions will be defined here.
export const EMAIL_CHANGED = 'EMAIL_CHANGED';
export const PASSWORD_CHANGED = 'PASSWORD_CHANGED';
export const LOGIN_USER = 'LOGIN_USER';
export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
export const LOGIN_USER_FAIL = 'LOGIN_USER_FAIL';
Action creators: functions that create actions.
E.g. Auth.ts: all actions related authentication.
function emailChanged(email: string) {
return {
type: EMAIL_CHANGED,
payload: email
};
}
- To automatically bind action creators to a
dispatch()
, you can usebindActionCreators()
, we will talk about this later on.
- Reducers
Shape actions
Reducers specify how the application's state changes in response to actions sent to the store. Remember that actions only describe what happened, but don't describe how the application's state changes.
In this auth example, we want to store email and password. the initial state will be:
const initialState = {
email: '',
password: ''
};
The fomula to handle actions is (previousState, action) => nextState
function bypass(state = initialState, action: { type: string; payload: any }) {
switch (action.type) {
case EMAIL_CHANGED:
// update object by using ES6 spread operator
return { ...state, email: action.payload };
default:
return state;
}
}
A reducer example here: Bypass.ts
Since we have more reducers, we combine reducers
import { combineReducers } from 'redux';
import { bypass } from './Bypass';
// combine all reducers here
const appReducers = combineReducers({
bypass,
// reducer2,
// reducer3,
// other reducers
});
export default appReducers;
- Store
The store has the following responsibilities:
- Holds application state;
- Allows access to state via
getState()
; - Allows state to be updated via
dispatch(action)
; - Registers listeners via
subscribe(listener)
; - Handles unregistering of listeners via the function returned by
subscribe(listener)
.
Create the store.
import { createStore } from 'redux';
const store = createStore(appReducers);
- React redux
<Provider />
The <Provider />
makes the Redux store available to any nested components that have been wrapped in the connect()
function.
conncet()
React Redux provides a connect
function for you to connect your component to the store.
Redux middleware
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.
User-defined middleware
E.g. create a Logger middle to keep redux log
Logger.ts
const Logger = (store: any) => (next: any) => (action: any) => {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result;
};
export default Logger;
Apply the middleware
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(appReducers, composeEnhancer(applyMiddleware(ReduxThunk, Logger)));
Aasynchronous data flow
Three popular redux middleware: redux-thunk, redux-promise and redux-saga to run asynchronous data flow in redux store.
- Redux-thunk
- Install redux-thunk
yarn add redux-thunk
- Apply middleware
import ReduxThunk from 'redux-thunk';
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(appReducers, composeEnhancer(applyMiddleware(ReduxThunk)));
Explore more middlewares
You can use redix-promise or redux-promise-middleware to dispatch Promises instead of functions.
You can use redux-observable to dispatch Observables.
You can use the redux-saga middleware to build more complex asynchronous actions.
You can use the redux-pack middleware to dispatch promise-based asynchronous actions.
You can even write a custom middleware to describe calls to your API, like the real world example does.