/React-Native-Boilerplate

⚛️🚀React Native boilerplate (Typescript + redux + redux-thunk)

Primary LanguageTypeScriptMIT LicenseMIT

RNBoilerplate

CircleCI

React Native boilerplate (Typescript + react-navigation + redux + redux-thunk)

ESLint and Prettier

  1. 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'
    ]
};
  1. add .prettierrc
{
    "printWidth": 100,
    "semi": true,
    "singleQuote": true,
    "trailingComma": "none",
    "bracketSpacing": true,
    "arrowParens": "avoid",
    "proseWrap": "never",
    "tabWidth": 4,P
    "jsxBracketSameLine": true
}
  1. install ESLint and Prettier
yarn add babel-eslint
yarn add -D eslint prettier @react-native-community/eslint-config eslint-config-prettier eslint-config-typescript
  1. install ESLint extention on Vistual Studio Code

Link

  1. 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
}

vscode-settings.json

  1. Restart Vistual Studio Code

Typescript

  1. 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"]
}
  1. install dependencies
yarn add typescript @types/jest @types/react @types/react-native @types/react-test-renderer
  1. eslint and prettier settings
    • install dev dependency: yarn add -D eslint-config-typescript
    • add typescript config in eslintrc.js
module.exports = {
    extends: [
        'prettier/@typescript-eslint'
    ]
};
  1. rename .js files to .ts files. DO NOT change index.js (where your AppRegistry is called) or circleci may build failed
  2. rename files with JSX elements to .tsx files

Redux

Prerequisites

  1. install react-native-debugger
  2. 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)));

Learn more

  1. open debugger
open "rndebugger://set-debugger-loc?host=localhost&port=8081"

This port is equal to metro bundler port

  1. Switch to dark theme and restart the debugger

Enable Dark Theme In Chrome DevTools

  1. Enable debug on your app

+ D => click debug

  1. 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
    };
}
  1. To automatically bind action creators to a dispatch(), you can use bindActionCreators(), we will talk about this later on.

Learn more

  • 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

index.ts

import { combineReducers } from 'redux';
import { bypass } from './Bypass';

// combine all reducers here
const appReducers = combineReducers({
    bypass,
    // reducer2,
    // reducer3,
    // other reducers
});
export default appReducers;

Learn more

  • Store

The store has the following responsibilities:

  1. Holds application state;
  2. Allows access to state via getState();
  3. Allows state to be updated via dispatch(action);
  4. Registers listeners via subscribe(listener);
  5. Handles unregistering of listeners via the function returned by subscribe(listener).

Create the store.

import { createStore } from 'redux';
const store = createStore(appReducers);

Learn more

  • React redux
  1. <Provider />

The <Provider /> makes the Redux store available to any nested components that have been wrapped in the connect() function.

RootNavigator.tsx

  1. conncet()

React Redux provides a connect function for you to connect your component to the store.

SignIn.tsx

Learn more

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)));

Logger.ts

learn more

Aasynchronous data flow

Three popular redux middleware: redux-thunk, redux-promise and redux-saga to run asynchronous data flow in redux store.

  • Redux-thunk
  1. Install redux-thunk
yarn add redux-thunk
  1. Apply middleware
import ReduxThunk from 'redux-thunk';
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(appReducers, composeEnhancer(applyMiddleware(ReduxThunk)));

redux-thunk

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.