├── .watchmanconfig ├── renovate.json ├── src ├── constants.ts ├── states │ ├── saga.ts │ ├── sample │ │ ├── sample-actions.ts │ │ ├── sample-saga.ts │ │ ├── sample-reducer.ts │ │ └── sample.test.ts │ └── store.ts └── environments.ts ├── assets ├── icon.png ├── splash.png └── screenshot.png ├── .gitignore ├── .vscode └── settings.json ├── babel.config.js ├── .expo-shared └── assets.json ├── README.md ├── app.json ├── tsconfig.json ├── package.json ├── tslint.json └── App.tsx /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SampleConstants = { 2 | hoge: "fuga", 3 | }; 4 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawachan/react-native-expo-boilerplate/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawachan/react-native-expo-boilerplate/HEAD/assets/splash.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.tslintIntegration": true 4 | } 5 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tawachan/react-native-expo-boilerplate/HEAD/assets/screenshot.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: [] 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/states/saga.ts: -------------------------------------------------------------------------------- 1 | import { all, fork } from "redux-saga/effects"; 2 | import { sampleSagaWorkers } from "./sample/sample-saga"; 3 | 4 | export function* sagaFunctions() { 5 | yield all([fork(sampleSagaWorkers)]); 6 | } 7 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "7adc6cdde1172c646f8dda7fcb1186d148e59e6d2a40774bd7e03281a653f19c": true, 3 | "5efae8e1083638bdf1ee77188f2b1ebdab54f729a0fec67a418649c7840d0f47": true, 4 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 5 | } -------------------------------------------------------------------------------- /src/states/sample/sample-actions.ts: -------------------------------------------------------------------------------- 1 | import actionCreatorFactory from "typescript-fsa"; 2 | 3 | const actionCreator = actionCreatorFactory("SAMPLE"); 4 | 5 | export const sampleActions = { 6 | fetchSamplesAsync: actionCreator.async<{}, { text: string }>("FETCH_SAMPLES_ASYNC"), 7 | fetchSamplesSync: actionCreator<{}>("FETCH_SAMPLES_SYNC"), 8 | }; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-expo-boilerplate 2 | 3 | This is a boilerplate to develop a mobile app with React Native and Expo(v33) using TypeScript and Redux (Redux Saga). 4 | 5 | Things already configured: 6 | 7 | - Redux 8 | - Redux Saga 9 | - tslint 10 | - prettier 11 | - jest 12 | 13 | 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-expo-boilerplate", 4 | "slug": "react-native-expo-boilerplate", 5 | "privacy": "public", 6 | "sdkVersion": "33.0.0", 7 | "platforms": ["ios", "android"], 8 | "version": "1.0.0", 9 | "orientation": "portrait", 10 | "icon": "./assets/icon.png", 11 | "splash": { 12 | "image": "./assets/splash.png", 13 | "resizeMode": "contain", 14 | "backgroundColor": "#ffffff" 15 | }, 16 | "updates": { 17 | "fallbackToCacheTimeout": 0 18 | }, 19 | "assetBundlePatterns": ["**/*"], 20 | "ios": { 21 | "supportsTablet": true 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/states/store.ts: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, combineReducers, createStore } from "redux"; 2 | import createSagaMiddleware from "redux-saga"; 3 | import { composeWithDevTools } from "remote-redux-devtools"; 4 | import { sagaFunctions } from "./saga"; 5 | import { sampleReducer, SampleState } from "./sample/sample-reducer"; 6 | 7 | const sagaMiddleware = createSagaMiddleware(); 8 | const composeEnhancers = composeWithDevTools(applyMiddleware(sagaMiddleware)); 9 | 10 | export interface AppState { 11 | samples: SampleState; 12 | } 13 | 14 | export const store = createStore( 15 | combineReducers({ 16 | samples: sampleReducer, 17 | }), 18 | composeEnhancers 19 | ); 20 | 21 | sagaMiddleware.run(sagaFunctions); 22 | -------------------------------------------------------------------------------- /src/states/sample/sample-saga.ts: -------------------------------------------------------------------------------- 1 | import { SagaIterator } from "redux-saga"; 2 | import { takeLatest } from "redux-saga/effects"; 3 | import { Action } from "typescript-fsa"; 4 | import { bindAsyncAction } from "typescript-fsa-redux-saga-extended"; 5 | import { sampleActions } from "./sample-actions"; 6 | 7 | const sampleFetchSamplesAsyncWorker = bindAsyncAction(sampleActions.fetchSamplesAsync)(function*( 8 | _: Action<{}> 9 | ): SagaIterator { 10 | // const response = yield call(asyncFunction, someInputs); 11 | return { text: "new value generated by asynchronized action" }; 12 | }); 13 | 14 | export function* sampleSagaWorkers() { 15 | yield takeLatest(sampleActions.fetchSamplesAsync.started, sampleFetchSamplesAsyncWorker); 16 | } 17 | -------------------------------------------------------------------------------- /src/states/sample/sample-reducer.ts: -------------------------------------------------------------------------------- 1 | import { reducerWithInitialState } from "typescript-fsa-reducers"; 2 | import { sampleActions } from "./sample-actions"; 3 | 4 | export interface SampleState { 5 | text: string; 6 | } 7 | 8 | export const initialState: SampleState = { 9 | text: "initial value", 10 | }; 11 | 12 | export const sampleReducer = reducerWithInitialState(initialState) 13 | .case(sampleActions.fetchSamplesAsync.done, (state, payload) => { 14 | return Object.assign({}, state, { 15 | text: payload.result.text, 16 | }); 17 | }) 18 | .case(sampleActions.fetchSamplesSync, (state, payload) => { 19 | return Object.assign({}, state, { 20 | text: "new value generated by synchronized action", 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "forceConsistentCasingInFileNames": false, 5 | "declaration": false, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "strictNullChecks": true, 9 | "baseUrl": ".", 10 | "jsx": "react-native", 11 | "lib": ["dom", "es2017", "es2018"], 12 | "module": "es2015", 13 | "moduleResolution": "node", 14 | "sourceMap": true, 15 | "target": "es2015", 16 | "paths": { 17 | "@app/env": ["environments/environment"] 18 | } 19 | }, 20 | "include": ["src/**/*", "App.tsx"], 21 | "exclude": ["node_modules"], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/states/sample/sample.test.ts: -------------------------------------------------------------------------------- 1 | import { sampleActions as actions } from "./sample-actions"; 2 | import { initialState, sampleReducer as reducer } from "./sample-reducer"; 3 | 4 | describe("SAMPLE", () => { 5 | describe("FETCH_SAMPLES_ASYNC", () => { 6 | const action = { 7 | type: actions.fetchSamplesAsync.done.type, 8 | payload: { result: { text: "Async" } }, 9 | }; 10 | 11 | it("checks value is Async", () => { 12 | const state = reducer(initialState, action); 13 | expect(state.text).toEqual("Async"); 14 | }); 15 | }); 16 | 17 | describe("FETCH_SAMPLES_SYNC", () => { 18 | const action = { 19 | type: actions.fetchSamplesSync.type, 20 | payload: { text: "Sync" }, 21 | }; 22 | 23 | it("checks value is Sync", () => { 24 | const state = reducer(initialState, action); 25 | expect(state.text).toEqual("Sync"); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/environments.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "expo"; 2 | 3 | const ENVs = { 4 | dev: { 5 | environment: "development" as "development" | "staging" | "production", 6 | apiUrl: "http://localhost:3000", 7 | }, 8 | staging: { 9 | environment: "staging" as "development" | "staging" | "production", 10 | apiUrl: "https://staging.com", 11 | }, 12 | production: { 13 | environment: "production" as "development" | "staging" | "production", 14 | apiUrl: "https://production.com", 15 | }, 16 | }; 17 | 18 | function getEnvVars() { 19 | const options = Constants.manifest.packagerOpts; 20 | const channel = Constants.manifest.releaseChannel; 21 | const isDev = options != null ? options.dev : true; 22 | if (isDev) { 23 | return ENVs.dev; 24 | } else { 25 | if (channel === "production") { 26 | return ENVs.production; 27 | } else { 28 | return ENVs.staging; 29 | } 30 | } 31 | } 32 | 33 | export const ENV = getEnvVars(); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "eject": "expo eject", 8 | "tslint": "tslint --project ./ --fix", 9 | "test": "jest" 10 | }, 11 | "dependencies": { 12 | "expo": "^33.0.0", 13 | "react": "16.8.3", 14 | "react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz", 15 | "react-redux": "^6.0.0", 16 | "redux": "^4.0.1", 17 | "redux-saga": "^0.16.0", 18 | "tslib": "1.9.3", 19 | "typescript-fsa-reducers": "^1.2.0", 20 | "typescript-fsa-redux-saga-extended": "^1.0.6" 21 | }, 22 | "devDependencies": { 23 | "@types/expo": "32.0.13", 24 | "@types/expo__vector-icons": "9.0.0", 25 | "@types/jest": "^23.3.13", 26 | "@types/react": "16.7.22", 27 | "@types/react-native": "0.57.32", 28 | "@types/react-redux": "^7.0.1", 29 | "babel-preset-expo": "^5.0.0", 30 | "jest": "^24.0.0", 31 | "prettier": "^1.16.3", 32 | "remote-redux-devtools": "^0.5.16", 33 | "tslint": "^5.12.1", 34 | "tslint-config-prettier": "^1.17.0", 35 | "tslint-plugin-prettier": "^2.0.1", 36 | "typescript": "3.3.1" 37 | }, 38 | "private": true 39 | } 40 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rulesDirectory": ["tslint-plugin-prettier"], 4 | "rules": { 5 | "prettier": [ 6 | true, 7 | { 8 | "singleQuote": false, 9 | "trailingComma": "es5", 10 | "printWidth": 120 11 | } 12 | ], 13 | "no-duplicate-variable": true, 14 | "no-angle-bracket-type-assertion": false, 15 | "no-reference": false, 16 | "curly": true, 17 | "indent": [true, "spaces"], 18 | "no-conditional-assignment": true, 19 | "no-debugger": true, 20 | "no-console": [true, "log", "debug", "info", "time", "timeEnd", "trace"], 21 | "no-consecutive-blank-lines": true, 22 | "no-eval": true, 23 | "no-namespace": false, 24 | "no-switch-case-fall-through": true, 25 | "no-trailing-whitespace": true, 26 | "no-unused-expression": true, 27 | "no-use-before-declare": false, 28 | "no-use-before-define": ["error", { "functions": false, "classes": true }], 29 | "no-shadowed-variable": false, 30 | "one-line": [ 31 | true, 32 | "check-catch", 33 | "check-else", 34 | "check-open-brace", 35 | "check-whitespace" 36 | ], 37 | "semicolon": [true, "always", "ignore-bound-class-methods"], 38 | "switch-default": true, 39 | "triple-equals": [true, "allow-null-check"], 40 | "object-literal-sort-keys": false, 41 | "interface-name": false, 42 | "variable-name": [ 43 | true, 44 | "ban-keywords", 45 | "check-format", 46 | "allow-leading-underscore", 47 | "allow-pascal-case", 48 | "allow-snake-case" 49 | ], 50 | "whitespace": [ 51 | true, 52 | "check-branch", 53 | "check-decl", 54 | "check-operator", 55 | "check-module", 56 | "check-separator", 57 | "check-type", 58 | "check-typecast" 59 | ], 60 | "max-line-length": [true, 170], 61 | "max-classes-per-file": [true, 2] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch } from "react"; 2 | import { Button, StyleSheet, Text, View } from "react-native"; 3 | import { connect, Provider } from "react-redux"; 4 | import { Action } from "typescript-fsa"; 5 | import { sampleActions } from "./src/states/sample/sample-actions"; 6 | import { SampleState } from "./src/states/sample/sample-reducer"; 7 | import { AppState, store } from "./src/states/store"; 8 | 9 | interface BaseProps { 10 | samples: SampleState; 11 | fetchSamplesAsync: () => Action; 12 | fetchSamplesSync: () => Action; 13 | } 14 | 15 | export default class App extends React.Component<{}> { 16 | public render() { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | 27 | class Base extends React.Component { 28 | public render() { 29 | const { fetchSamplesAsync, fetchSamplesSync, samples } = this.props; 30 | return ( 31 | 38 | 47 | Thank you for trying 48 | react-native-expo-boilerplate 49 | with TypeScript! 50 | 51 | This boilerplate already implements 52 | the libraries below: 53 | 54 | - Redux 55 | - Redux Saga 56 | - tslint 57 | - prettier 58 | 59 | 60 |