diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 8da7796..0000000 --- a/.eslintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": "airbnb", - "parser": "babel-eslint", - "parserOptions": { - "ecmaFeatures": { - "classes": true - } - }, - "rules": { - "react/jsx-filename-extension": [ - "error", - { - "extensions": [".js", ".jsx"] - } - ], - "no-new": "warn", - "global-require": "off", - "no-console": "error", - "import/no-extraneous-dependencies": "off", - "import/extensions": "off", - "import/no-unresolved": "off", - "jsx-a11y/anchor-is-valid": "off", - "no-underscore-dangle": "error", - "prefer-promise-reject-errors": "error", - "no-nested-ternary": "error", - "react/no-multi-comp": "off", - "comma-dangle": 0, - "prefer-destructuring": 1 - }, - "env": { - "jest": true - }, - "globals": { - "__DEV__": true - } -} diff --git a/.travis.yml b/.travis.yml index 43d67ea..9691e50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ jobs: language: node_js node_js: 8.11.2 install: yarn - script: npm run lint + script: yarn lint - stage: Build Android 🤖 before_install: - sudo apt-get install build-essential checkinstall && sudo apt-get install libssl-dev diff --git a/README.md b/README.md index a9d2530..c1e595d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![License](https://img.shields.io/github/license/AmitM30/react-native-typescript-boilerplate.svg) ![Build](https://build.appcenter.ms/v0.1/apps/d3466edd-c992-45c8-abd3-a2f40f6a7fa1/branches/master/badge) -### An opinionated [React Native](https://facebook.github.io/react-native/docs/getting-started) Starter Kit with [React Native Navigation](https://github.com/wix/react-native-navigation) + [Redux](https://github.com/reactjs/redux) + [Eslint](https://github.com/airbnb/javascript) to build iOS / Android apps using [TypeScript](https://github.com/Microsoft/TypeScript-React-Native-Starter) +### An opinionated [React Native](https://facebook.github.io/react-native/docs/getting-started) Starter Kit with [React Native Navigation](https://github.com/wix/react-native-navigation) + [Redux](https://github.com/reactjs/redux) + [TSLint](https://github.com/airbnb/javascript) to build iOS / Android apps using [TypeScript](https://github.com/Microsoft/TypeScript-React-Native-Starter) The project has been setup based off [RN Getting Started](https://facebook.github.io/react-native/docs/getting-started) and instructions from [Microsoft's Github TypeScript React Native Starter](https://github.com/Microsoft/TypeScript-React-Native-Starter) repo. @@ -17,7 +17,11 @@ You might also want to [rename](https://medium.com/the-react-native-log/how-to-r _Disclaimer_: This is an **opinionated** approach to building apps with RN. The project structure is inspired by multiple production apps built by the contributors. -The project uses and encourages to use industry best practices / tools / libraries like RNN, redux, eslint, separation of concern and structure to build a maintainable app. +The project uses and encourages to use industry best practices / tools / libraries like RNN, redux, tslint, separation of concern and structure to build a maintainable app. + +| ![Splash](https://github.com/AmitM30/react-native-typescript-boilerplate/tree/master/src/view/assets/images/sample/1.png "Splash") | ![Home](https://github.com/AmitM30/react-native-typescript-boilerplate/tree/master/src/view/assets/images/sample/2.png "Home") | +| :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | + ### Table of Contents @@ -62,10 +66,11 @@ The project uses and encourages to use industry best practices / tools / librari │ ├── presentation │ └── redux ├── .babelrc -├── .eslintrc Lint configuration - extending AirBnb ├── .gitignore -├── .travis.yml Travis CI +├── .travis.yml Travis CI ├── tsconfig.json TypeScript Configuration +├── tslint.js TSLint configuration - extending AirBnb +├── tsconfig.json ├── app.json ├── index.js Application Entry point ├── package.json @@ -113,16 +118,16 @@ and the launch from IDE. #### Lint -To run lint on the application: +To run tslint on the application: ``` -npm run lint +yarn lint ``` -To fix lint issues automatically +To fix most tslint issues automatically ``` -npm run lint:fix +yarn lint:fix ``` #### Unit Test @@ -166,7 +171,7 @@ Please check out [Contributing](https://github.com/AmitM30/react-native-typescri #### Authors -- **Anurag Chutani** - _Android Setup_ - [Profile](https://github.com/a7urag) +- [**Anurag Chutani**](https://github.com/a7urag) - _Android Setup_ See also the list of [contributors](https://github.com/AmitM30/react-native-typescript-boilerplate/contributors) who participated in this project. diff --git a/__tests__/App.tsx b/__tests__/App.tsx index db4a9f6..4b154ee 100644 --- a/__tests__/App.tsx +++ b/__tests__/App.tsx @@ -4,12 +4,12 @@ */ import 'react-native'; -import renderer from 'react-test-renderer'; +import reactTestRenderer from 'react-test-renderer'; -import App from '../src/navigators'; +import * as App from '../src/navigators'; // Note: test renderer must be required after react-native. it('renders correctly', () => { - renderer.create(App()); + reactTestRenderer.create(App()); }); diff --git a/__tests__/views/home.tsx b/__tests__/views/home.tsx index c5354f9..936d996 100644 --- a/__tests__/views/home.tsx +++ b/__tests__/views/home.tsx @@ -1,11 +1,10 @@ -import React from 'react'; import 'react-native'; -import renderer from 'react-test-renderer'; +import reactTestRenderer from 'react-test-renderer'; -import Home from '../../src/view/screens/home'; +import * as Home from '../../src/view/screens/home'; // Note: test renderer must be required after react-native. it('renders correctly with defaults', () => { - const tree = renderer.create().toJSON(); + const tree = reactTestRenderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/index.js b/index.js index 1798e1c..0e36f5b 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,7 @@ -// /** -// * @format -// * @lint-ignore-every XPLATJSCOPYRIGHT1 -// */ - -// import { AppRegistry } from "react-native"; -// import App from "./App"; -// import { name as appName } from "./app.json"; - -// AppRegistry.registerComponent(appName, () => App); - -import App from './src/navigators'; +/** + * @format + * @lint-ignore-every XPLATJSCOPYRIGHT1 + */ +import App from "./src/navigators"; App(); diff --git a/package.json b/package.json index 8ab104a..3dd6e2f 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "start": "node node_modules/react-native/local-cli/cli.js start", "test": "jest", "android": "react-native run-android --variant=Debug", - "lint": "eslint .", - "lint:fix": "npm run lint -- --fix" + "lint": "tslint --project ./tsconfig.json", + "lint:fix": "tslint --fix --project ./tsconfig.json" }, "dependencies": { "react": "16.6.3", @@ -67,6 +67,8 @@ "react-test-renderer": "16.6.3", "regenerator-runtime": "^0.13.1", "ts-jest": "^23.10.5", + "tslint": "^5.12.1", + "tslint-config-airbnb": "^5.11.1", "typescript": "^3.2.4" }, "jest": { diff --git a/shared/redux/constants/actionTypes.tsx b/shared/redux/constants/actionTypes.tsx index 9bbdda2..efe6068 100644 --- a/shared/redux/constants/actionTypes.tsx +++ b/shared/redux/constants/actionTypes.tsx @@ -1,8 +1,8 @@ /* Add All Action constants here */ -const ActionTypes = { +const ACTION_TYPES = { // Splash Actions - SPLASH_LAUNCHED: 'SPLASH_LAUNCHED' + SPLASH_LAUNCHED: 'SPLASH_LAUNCHED', }; -export default ActionTypes; +export { ACTION_TYPES }; diff --git a/shared/redux/reducers/app.tsx b/shared/redux/reducers/app.tsx index 58d5639..05e22dd 100644 --- a/shared/redux/reducers/app.tsx +++ b/shared/redux/reducers/app.tsx @@ -1,14 +1,14 @@ -import ActionTypes from '../constants/actionTypes'; +import { ACTION_TYPES } from '../constants/actionTypes'; const initialState = { - isLoading: false + isLoading: false, }; -export default (state = initialState, action) => { +export default (state = initialState, action: any) => { switch (action.type) { - case ActionTypes.SPLASH_LAUNCHED: + case ACTION_TYPES.SPLASH_LAUNCHED: return { - ...state + ...state, }; default: return state; diff --git a/shared/redux/reducers/index.tsx b/shared/redux/reducers/index.tsx index 4817673..9dd1c50 100644 --- a/shared/redux/reducers/index.tsx +++ b/shared/redux/reducers/index.tsx @@ -8,5 +8,5 @@ import { combineReducers } from 'redux'; import app from './app'; export default combineReducers({ - app + app, }); diff --git a/shared/redux/store/index.tsx b/shared/redux/store/index.tsx index cd2a1aa..c04fda9 100644 --- a/shared/redux/store/index.tsx +++ b/shared/redux/store/index.tsx @@ -1,10 +1,10 @@ import { applyMiddleware, createStore } from 'redux'; -import thunkMiddleware from 'redux-thunk'; +import * as thunkMiddleware from 'redux-thunk'; import { createLogger } from 'redux-logger'; import reducers from '../reducers'; -let middlewares = [thunkMiddleware]; +let middlewares = [thunkMiddleware.default]; if (__DEV__) { const loggerMiddleware = createLogger(); middlewares = [...middlewares, loggerMiddleware]; diff --git a/src/constants/screen.tsx b/src/constants/screen.tsx index 9a9fbe5..1fb5ee0 100644 --- a/src/constants/screen.tsx +++ b/src/constants/screen.tsx @@ -1,9 +1,9 @@ // Screen ids constants -const Screens = { +const SCREENS = { Splash: 'Splash', Home: 'Home', - Settings: 'Settings' + Settings: 'Settings', }; -export default Screens; +export { SCREENS }; diff --git a/src/navigators/index.tsx b/src/navigators/index.tsx index 066d561..2c3bc8a 100644 --- a/src/navigators/index.tsx +++ b/src/navigators/index.tsx @@ -2,7 +2,7 @@ import { Navigation } from 'react-native-navigation'; import { Provider } from 'react-redux'; import store from '../../shared/redux/store'; -import registerScreens from '../view/screens'; +import { registerScreens } from '../view/screens'; import { showSplash } from './navigation'; /** @@ -10,16 +10,14 @@ import { showSplash } from './navigation'; */ registerScreens({ store, Provider }); -const App = () => { +const app = () => { Navigation.events().registerAppLaunchedListener(() => { Navigation.setDefaultOptions({ - topBar: { - visible: true - } + topBar: { visible: true }, }); showSplash(); }); }; -export default App; +export default app; diff --git a/src/navigators/navigation.tsx b/src/navigators/navigation.tsx index 1f235ba..0357d29 100644 --- a/src/navigators/navigation.tsx +++ b/src/navigators/navigation.tsx @@ -1,89 +1,88 @@ import { Navigation } from 'react-native-navigation'; -import Screens from '../constants/screen'; -import TYPOGRAPHY from '../view/styles/typography'; +import { SCREENS } from '../constants/screen'; +import { TYPOGRAPHY } from '../view/styles/typography'; export const showSplash = () => { Navigation.setRoot({ root: { - component: { - name: Screens.Splash - } - } + component: { name: SCREENS.Splash }, + }, }); }; -export const tabbedNavigation = () => Navigation.setRoot({ - root: { - bottomTabs: { - id: 'BottomTabsId', - children: [ - { - stack: { - children: [ - { - component: { - name: Screens.Home, - passProps: { - text: 'This is Home' +export const tabbedNavigation = () => + Navigation.setRoot({ + root: { + bottomTabs: { + id: 'BottomTabsId', + children: [ + { + stack: { + children: [ + { + component: { + name: SCREENS.Home, + passProps: { + text: 'This is Home', + }, + options: { + topBar: { + visible: false, + drawBehind: true, + animate: true, + }, + bottomTab: { + fontSize: 12, + text: 'Home', + textColor: TYPOGRAPHY.COLOR.Primary, + selectedTextColor: TYPOGRAPHY.COLOR.Secondary, + icon: require('../view/assets/images/tabbar/home.png'), + selectedIcon: require('../view/assets/images/tabbar/home.png'), + }, + }, }, - options: { - topBar: { - visible: false, - drawBehind: true, - animate: true + }, + ], + }, + }, + { + stack: { + children: [ + { + component: { + name: SCREENS.Settings, + options: { + topBar: { + visible: false, + drawBehind: true, + animate: true, + }, + bottomTab: { + text: 'Settings', + fontSize: 12, + textColor: TYPOGRAPHY.COLOR.Primary, + selectedTextColor: TYPOGRAPHY.COLOR.Secondary, + icon: require('../view/assets/images/tabbar/settings.png'), + selectedIcon: require('../view/assets/images/tabbar/settings.png'), + }, }, - bottomTab: { - fontSize: 12, - text: 'Home', - textColor: TYPOGRAPHY.Color.Primary, - selectedTextColor: TYPOGRAPHY.Color.Primary, - icon: require('../view/assets/images/tabbar/home.png'), - selectedIcon: require('../view/assets/images/tabbar/home.png') - } - } - } - } - ] - } + }, + }, + ], + }, + }, + ], + options: { + bottomTabs: { + visible: true, + titleDisplayMode: 'alwaysShow', + backgroundColor: TYPOGRAPHY.COLOR.Default, + drawBehind: true, + }, }, - { - stack: { - children: [ - { - component: { - name: Screens.Settings, - options: { - topBar: { - visible: false, - drawBehind: true, - animate: true - }, - bottomTab: { - text: 'Settings', - fontSize: 12, - textColor: TYPOGRAPHY.Color.Primary, - selectedTextColor: TYPOGRAPHY.Color.Primary, - icon: require('../view/assets/images/tabbar/settings.png'), - selectedIcon: require('../view/assets/images/tabbar/settings.png') - } - } - } - } - ] - } - } - ], - options: { - bottomTabs: { - visible: true, - titleDisplayMode: 'alwaysShow', - backgroundColor: TYPOGRAPHY.Color.Default, - drawBehind: true - } - } - } - } -}); + }, + }, + }); export default tabbedNavigation; diff --git a/src/view/assets/images/rn_ts.png b/src/view/assets/images/rn_ts.png new file mode 100644 index 0000000..edf1339 Binary files /dev/null and b/src/view/assets/images/rn_ts.png differ diff --git a/src/view/assets/images/rnn2.png b/src/view/assets/images/rnn2.png new file mode 100644 index 0000000..998b8c9 Binary files /dev/null and b/src/view/assets/images/rnn2.png differ diff --git a/src/view/assets/images/sample/1.png b/src/view/assets/images/sample/1.png new file mode 100644 index 0000000..858249d Binary files /dev/null and b/src/view/assets/images/sample/1.png differ diff --git a/src/view/assets/images/sample/2.png b/src/view/assets/images/sample/2.png new file mode 100644 index 0000000..ef6b28f Binary files /dev/null and b/src/view/assets/images/sample/2.png differ diff --git a/src/view/elements/buttons.tsx b/src/view/elements/buttons.tsx new file mode 100644 index 0000000..cfb03fe --- /dev/null +++ b/src/view/elements/buttons.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { TouchableOpacity } from 'react-native'; + +import { GLOBAL } from '../styles/global'; +import { CText } from './custom'; + +type Callback = () => any; +export interface Props { + title: string; + onClick: Callback; + style?: Text.propTypes.style; +} + +/** + * Default Button + */ +const BUTTON_DEFAULT = ({ title, onClick, style }: Props) => ( + onClick()} + > + {title} + +); + +export { BUTTON_DEFAULT }; diff --git a/src/view/elements/custom.tsx b/src/view/elements/custom.tsx new file mode 100644 index 0000000..17c187b --- /dev/null +++ b/src/view/elements/custom.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { Text } from 'react-native'; + +import { GLOBAL } from '../styles/global'; + +export interface Props { + style: Text.propTypes.style; +} + +interface State {} + +class CText extends React.PureComponent { + static defaultProps = { + style: GLOBAL.TEXT.Default, + }; + + render() { + const { style, children } = this.props; + + return ( + + {children} + + ); + } +} + +export { CText }; diff --git a/src/view/screens/home/Component.tsx b/src/view/screens/home/Component.tsx index 45dd5cc..840ff49 100644 --- a/src/view/screens/home/Component.tsx +++ b/src/view/screens/home/Component.tsx @@ -1,7 +1,8 @@ -import React, { PureComponent } from 'react'; -import { View, Text } from 'react-native'; +import * as React from 'react'; +import { View } from 'react-native'; import styles from './styles'; +import { CText } from '../../elements/custom'; export interface Props { name: string; @@ -11,11 +12,11 @@ interface State { name: string; } -class Home extends PureComponent { +class Home extends React.PureComponent { constructor(props: Props) { super(props); this.state = { - name: props.name || 'Amit' + name: props.name || 'RN + TS + RNN2', }; } @@ -26,8 +27,8 @@ class Home extends PureComponent { return ( - Home - {name} + Home + {name} ); } diff --git a/src/view/screens/home/index.tsx b/src/view/screens/home/index.tsx index 9273664..520cb95 100644 --- a/src/view/screens/home/index.tsx +++ b/src/view/screens/home/index.tsx @@ -1,14 +1,14 @@ import { connect } from 'react-redux'; -import Home from './Component'; +import Component from './Component'; const mapStateToProps = () => ({}); const mapDispatchToProps = () => ({}); -const HomeContainer = connect( +const homeContainer = connect( mapStateToProps, - mapDispatchToProps -)(Home); + mapDispatchToProps, +)(Component); -export default HomeContainer; +export default homeContainer; diff --git a/src/view/screens/home/styles.tsx b/src/view/screens/home/styles.tsx index c35f24c..30c3d2e 100644 --- a/src/view/screens/home/styles.tsx +++ b/src/view/screens/home/styles.tsx @@ -1,13 +1,13 @@ import { StyleSheet } from 'react-native'; -import TYPOGRAPHY from '../../styles/typography'; +import { TYPOGRAPHY } from '../../styles/typography'; -const Styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, display: 'flex', - backgroundColor: TYPOGRAPHY.Color.Default - } + backgroundColor: TYPOGRAPHY.COLOR.Default, + }, }); -export default Styles; +export default styles; diff --git a/src/view/screens/index.tsx b/src/view/screens/index.tsx index badee8c..24a37ae 100644 --- a/src/view/screens/index.tsx +++ b/src/view/screens/index.tsx @@ -1,17 +1,25 @@ import { Navigation } from 'react-native-navigation'; -import Screens from '../../constants/screen'; +import { SCREENS } from '../../constants/screen'; -import Splash from './splash'; -import Home from './home'; -import Settings from './settings'; +import * as Splash from './splash'; +import * as Home from './home'; +import * as Settings from './settings'; -const registerComponentWithRedux = (redux: any) => (name: string, component: any) => { - Navigation.registerComponentWithRedux(name, () => component, redux.Provider, redux.store); +const registerComponentWithRedux = (redux: any) => ( + name: string, + component: any, +) => { + Navigation.registerComponentWithRedux( + name, + () => component, + redux.Provider, + redux.store, + ); }; -export default function registerScreens(redux: any) { - registerComponentWithRedux(redux)(Screens.Splash, Splash); - registerComponentWithRedux(redux)(Screens.Home, Home); - registerComponentWithRedux(redux)(Screens.Settings, Settings); +export function registerScreens(redux: any) { + registerComponentWithRedux(redux)(SCREENS.Splash, Splash.default); + registerComponentWithRedux(redux)(SCREENS.Home, Home.default); + registerComponentWithRedux(redux)(SCREENS.Settings, Settings.default); } diff --git a/src/view/screens/settings/Component.tsx b/src/view/screens/settings/Component.tsx index cc78fd2..6abcdce 100644 --- a/src/view/screens/settings/Component.tsx +++ b/src/view/screens/settings/Component.tsx @@ -1,13 +1,14 @@ -import React, { PureComponent } from 'react'; -import { View, Text } from 'react-native'; +import * as React from 'react'; +import { View } from 'react-native'; import styles from './styles'; +import { CText } from '../../elements/custom'; export interface Props {} interface State {} -class Settings extends PureComponent { +class Settings extends React.PureComponent { constructor(props: Props) { super(props); this.state = {}; @@ -18,7 +19,7 @@ class Settings extends PureComponent { render() { return ( - Settings + Settings ); } diff --git a/src/view/screens/settings/index.tsx b/src/view/screens/settings/index.tsx index 636628d..7aff87d 100644 --- a/src/view/screens/settings/index.tsx +++ b/src/view/screens/settings/index.tsx @@ -1,14 +1,14 @@ import { connect } from 'react-redux'; -import Settings from './Component'; +import Component from './Component'; const mapStateToProps = () => ({}); const mapDispatchToProps = () => ({}); -const SettingsContainer = connect( +const settingsContainer = connect( mapStateToProps, - mapDispatchToProps -)(Settings); + mapDispatchToProps, +)(Component); -export default SettingsContainer; +export default settingsContainer; diff --git a/src/view/screens/settings/styles.tsx b/src/view/screens/settings/styles.tsx index c35f24c..30c3d2e 100644 --- a/src/view/screens/settings/styles.tsx +++ b/src/view/screens/settings/styles.tsx @@ -1,13 +1,13 @@ import { StyleSheet } from 'react-native'; -import TYPOGRAPHY from '../../styles/typography'; +import { TYPOGRAPHY } from '../../styles/typography'; -const Styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, display: 'flex', - backgroundColor: TYPOGRAPHY.Color.Default - } + backgroundColor: TYPOGRAPHY.COLOR.Default, + }, }); -export default Styles; +export default styles; diff --git a/src/view/screens/splash/Component.tsx b/src/view/screens/splash/Component.tsx index a23d4a8..7810082 100644 --- a/src/view/screens/splash/Component.tsx +++ b/src/view/screens/splash/Component.tsx @@ -1,16 +1,15 @@ -import React, { PureComponent } from 'react'; -import { - View, Text, SafeAreaView, Button -} from 'react-native'; +import * as React from 'react'; +import { View, Image, SafeAreaView } from 'react-native'; import { tabbedNavigation } from '../../../navigators/navigation'; import styles from './styles'; +import { BUTTON_DEFAULT } from '../../elements/buttons'; export interface Props {} interface State {} -class Splash extends PureComponent { +class Splash extends React.PureComponent { constructor(props: Props) { super(props); this.state = {}; @@ -20,14 +19,25 @@ class Splash extends PureComponent { navigateToHome = () => { tabbedNavigation(); - }; + } render() { return ( - Splash -