├── .expo-shared └── assets.json ├── .github └── workflows │ ├── PR-preview.yml │ ├── publish.yml │ └── update.yml ├── .gitignore ├── App.tsx ├── README.md ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── eas.json ├── package.json ├── src ├── Components │ └── index.ts ├── Config │ └── index.ts ├── Hooks │ ├── index.ts │ └── redux.ts ├── Localization │ ├── index.ts │ ├── keys │ │ └── index.ts │ └── languages │ │ ├── en.ts │ │ ├── index.ts │ │ └── vi.ts ├── Navigation │ ├── Main │ │ └── index.tsx │ └── index.tsx ├── Screens │ ├── Home │ │ ├── Home.tsx │ │ ├── HomeContainer.tsx │ │ └── index.ts │ ├── Welcome │ │ ├── Welcome.tsx │ │ ├── WelcomeContainer.tsx │ │ └── index.ts │ └── index.ts ├── Services │ ├── base.ts │ ├── index.ts │ └── users │ │ └── index.ts ├── Store │ ├── index.ts │ └── reducers │ │ ├── home.ts │ │ ├── index.ts │ │ └── theme.ts ├── Theme │ ├── Variables.ts │ └── index.ts └── index.tsx ├── tsconfig.json └── yarn.lock /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/PR-preview.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Preview 2 | on: pull_request 3 | 4 | jobs: 5 | update: 6 | name: EAS Update 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check for EXPO_TOKEN 10 | run: | 11 | if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then 12 | echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" 13 | exit 1 14 | fi 15 | 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18.x 23 | cache: yarn 24 | 25 | - name: Setup EAS 26 | uses: expo/expo-github-action@v8 27 | with: 28 | eas-version: latest 29 | token: ${{ secrets.EXPO_TOKEN }} 30 | 31 | - name: Install dependencies 32 | run: yarn install 33 | 34 | - name: Publish update 35 | run: eas update --branch preview --message "${{ github.event.pull_request.title }}" 36 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | 13 | - name: Setup Node 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 16.x 17 | registry-url: https://registry.npmjs.org/ 18 | 19 | - name: Install dependencies 20 | run: yarn 21 | 22 | - name: Publish to NPM 23 | run: yarn publish 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Build Expo OTA 2 | on: push 3 | 4 | jobs: 5 | update: 6 | name: EAS Update 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check for EXPO_TOKEN 10 | run: | 11 | if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then 12 | echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" 13 | exit 1 14 | fi 15 | 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18.x 23 | cache: yarn 24 | 25 | - name: Setup EAS 26 | uses: expo/expo-github-action@v8 27 | with: 28 | eas-version: latest 29 | token: ${{ secrets.EXPO_TOKEN }} 30 | 31 | - name: Install dependencies 32 | run: yarn install 33 | 34 | - name: Publish update 35 | run: eas update --auto 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import App from "./src"; 2 | 3 | export default App; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Expo Redux Template 2 | [![NPM Version](https://img.shields.io/npm/v/react-native-expo-redux-template)](https://www.npmjs.com/package/react-native-expo-redux-template) 3 | [![Build Expo OTA](https://github.com/hpccbk/react-native-expo-redux-template/actions/workflows/update.yml/badge.svg)](https://github.com/hpccbk/react-native-expo-redux-template/actions/workflows/update.yml) 4 | 5 | 6 | This is a template for building React Native apps with the Expo framework and Redux for state management. It includes a basic file structure and configuration for setting up a Redux store, along with some example actions and reducers. 7 | 8 | ## Getting Started 9 | 10 | To use this template, you should have the Expo CLI installed on your system. You can install it globally using npm: 11 | 12 | ```bash 13 | npm install -g expo-cli 14 | ``` 15 | 16 | To create a new project using this template, please clone this repository. Then run: 17 | 18 | ```bash 19 | yarn install 20 | ``` 21 | 22 | ## File Structure 23 | 24 | The template's file structure is organized as follows: 25 | 26 | - `src/`: This folder contains the source code for the template, organized into sub-folders as follows: 27 | 28 | - `Components/`: This folder contains reusable components for the app. 29 | 30 | - `Config/`: This folder contains configuration files for the app, such as API endpoints, environment settings, and theme configuration. 31 | 32 | - `Hooks/`: This folder contains Redux and custom hooks for the app. 33 | 34 | - `Localization/`: This folder contains localization files for the app, allowing for easy translation to different languages. 35 | 36 | - `Navigation/`: This folder contains navigation files for the app, such as stack navigation, drawer navigation, or tab navigation. 37 | 38 | - `Screens/`: This folder contains the main screens of the app. 39 | 40 | - `Services/`: This folder contains service files for the app, such as API calls or Firebase integration. 41 | 42 | - `Store/`: This folder contains Redux-related files, including the store configuration and reducers. 43 | 44 | - `Theme/`: This folder contains files related to the app's visual theme, such as colors, typography, and spacing. 45 | 46 | ## Known Issues 47 | 48 | ```js 49 | ApiV2Error: Not Authorized. 50 | ``` 51 | Reason and how to remove this issue: https://github.com/expo/expo-cli/issues/2436#issuecomment-1308534521 52 | 53 | 54 | ## Contributing 55 | 56 | If you have suggestions for how this template could be improved, or want to report a bug, please open an issue or a pull request. We welcome contributions from the community! 57 | 58 | 59 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Expo Redux Template", 4 | "slug": "expo-redux-template", 5 | "version": "1.1.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "updates": { 15 | "fallbackToCacheTimeout": 0, 16 | "url": "https://u.expo.dev/82716b8e-2818-4161-8a88-c571df4369a5" 17 | }, 18 | "assetBundlePatterns": [ 19 | "**/*" 20 | ], 21 | "ios": { 22 | "supportsTablet": true, 23 | "bundleIdentifier": "com.hpcc.expo-redux-template" 24 | }, 25 | "android": { 26 | "adaptiveIcon": { 27 | "foregroundImage": "./assets/adaptive-icon.png", 28 | "backgroundColor": "#FFFFFF" 29 | }, 30 | "package": "com.hpcc.expo_redux_template" 31 | }, 32 | "web": { 33 | "favicon": "./assets/favicon.png" 34 | }, 35 | "plugins": [], 36 | "extra": { 37 | "eas": { 38 | "projectId": "82716b8e-2818-4161-8a88-c571df4369a5" 39 | } 40 | }, 41 | "runtimeVersion": { 42 | "policy": "sdkVersion" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: [ 6 | [ 7 | "module-resolver", 8 | { 9 | alias: { 10 | "@": "./src", 11 | }, 12 | }, 13 | ], 14 | ], 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 3.10.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal", 9 | "ios": { 10 | "resourceClass": "m-medium" 11 | } 12 | }, 13 | "preview": { 14 | "distribution": "internal", 15 | "ios": { 16 | "resourceClass": "m-medium" 17 | } 18 | }, 19 | "production": { 20 | "ios": { 21 | "resourceClass": "m-medium" 22 | } 23 | } 24 | }, 25 | "submit": { 26 | "production": {} 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-expo-redux-template", 3 | "version": "1.0.4", 4 | "repository": "https://github.com/hoanglehaithanh/react-native-expo-redux-template", 5 | "main": "node_modules/expo/AppEntry.js", 6 | "scripts": { 7 | "start": "expo start", 8 | "android": "expo start --android", 9 | "ios": "expo start --ios", 10 | "web": "expo start --web" 11 | }, 12 | "dependencies": { 13 | "@react-native-async-storage/async-storage": "1.17.11", 14 | "@react-native-community/netinfo": "9.3.7", 15 | "@react-navigation/bottom-tabs": "^6.4.0", 16 | "@react-navigation/native": "^6.0.13", 17 | "@react-navigation/native-stack": "^6.9.0", 18 | "@reduxjs/toolkit": "^1.8.5", 19 | "expo": "^48.0.0", 20 | "expo-camera": "~13.2.1", 21 | "expo-device": "~5.2.1", 22 | "expo-image-picker": "~14.1.1", 23 | "expo-localization": "~14.1.1", 24 | "expo-status-bar": "~1.4.4", 25 | "expo-updates": "~0.16.4", 26 | "i18n-js": "^4.1.1", 27 | "lottie-react-native": "5.1.4", 28 | "native-base": "^3.4.15", 29 | "react": "18.2.0", 30 | "react-dom": "18.2.0", 31 | "react-native": "0.71.6", 32 | "react-native-safe-area-context": "4.5.0", 33 | "react-native-screens": "~3.20.0", 34 | "react-native-svg": "13.4.0", 35 | "react-native-web": "~0.18.11", 36 | "react-redux": "^8.0.2", 37 | "redux-persist": "^6.0.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.20.0", 41 | "@types/react": "~18.0.27", 42 | "@types/react-native": "~0.69.1", 43 | "babel-plugin-module-resolver": "^4.1.0", 44 | "typescript": "^4.9.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Components/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/src/Components/index.ts -------------------------------------------------------------------------------- /src/Config/index.ts: -------------------------------------------------------------------------------- 1 | export const Config = { 2 | API_URL: "https://jsonplaceholder.typicode.com/", 3 | }; 4 | -------------------------------------------------------------------------------- /src/Hooks/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/src/Hooks/index.ts -------------------------------------------------------------------------------- /src/Hooks/redux.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/src/Hooks/redux.ts -------------------------------------------------------------------------------- /src/Localization/index.ts: -------------------------------------------------------------------------------- 1 | import { I18n } from "i18n-js"; 2 | import { en, vi } from "./languages"; 3 | 4 | export enum Language { 5 | "ENGLISH" = "en", 6 | "VIETNAMESE" = "vi", 7 | } 8 | 9 | // Set the key-value pairs for the different languages you want to support. 10 | export const i18n = new I18n({ 11 | [Language.VIETNAMESE]: vi, 12 | [Language.ENGLISH]: en, 13 | }); 14 | 15 | export * from "./keys"; 16 | -------------------------------------------------------------------------------- /src/Localization/keys/index.ts: -------------------------------------------------------------------------------- 1 | export enum LocalizationKey { 2 | WELCOME = "welcome", 3 | HOME = "home", 4 | START = "start", 5 | LOADING = "loading", 6 | } 7 | -------------------------------------------------------------------------------- /src/Localization/languages/en.ts: -------------------------------------------------------------------------------- 1 | import { LocalizationKey } from "../keys"; 2 | 3 | export const en = { 4 | [LocalizationKey.WELCOME]: "Welcome", 5 | [LocalizationKey.HOME]: "Home", 6 | [LocalizationKey.START]: "Start", 7 | [LocalizationKey.LOADING]: "Loading", 8 | }; 9 | -------------------------------------------------------------------------------- /src/Localization/languages/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./en"; 2 | export * from "./vi"; 3 | -------------------------------------------------------------------------------- /src/Localization/languages/vi.ts: -------------------------------------------------------------------------------- 1 | import { LocalizationKey } from "../keys"; 2 | 3 | export const vi = { 4 | [LocalizationKey.WELCOME]: "Xin chào", 5 | [LocalizationKey.HOME]: "Trang chủ", 6 | [LocalizationKey.START]: "Bắt đầu", 7 | [LocalizationKey.LOADING]: "Đang tải", 8 | }; 9 | -------------------------------------------------------------------------------- /src/Navigation/Main/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; 3 | import { HomeContainer } from "@/Screens/Home"; 4 | 5 | const Tab = createBottomTabNavigator(); 6 | 7 | // @refresh reset 8 | export const MainNavigator = () => { 9 | return ( 10 | 11 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/Navigation/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StatusBar } from "react-native"; 3 | import { createNativeStackNavigator } from "@react-navigation/native-stack"; 4 | import { NavigationContainer } from "@react-navigation/native"; 5 | import { MainNavigator } from "./Main"; 6 | import { WelcomeContainer } from "@/Screens/Welcome"; 7 | import { RootScreens } from "@/Screens"; 8 | 9 | export type RootStackParamList = { 10 | [RootScreens.MAIN]: undefined; 11 | [RootScreens.WELCOME]: undefined; 12 | }; 13 | 14 | const RootStack = createNativeStackNavigator(); 15 | 16 | // @refresh reset 17 | const ApplicationNavigator = () => { 18 | return ( 19 | 20 | 21 | 22 | 26 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export { ApplicationNavigator }; 37 | -------------------------------------------------------------------------------- /src/Screens/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { i18n, LocalizationKey } from "@/Localization"; 2 | import React from "react"; 3 | import { View, Text, StyleSheet } from "react-native"; 4 | import { StatusBar } from "expo-status-bar"; 5 | import { HStack, Spinner, Heading } from "native-base"; 6 | import { User } from "@/Services"; 7 | 8 | export interface IHomeProps { 9 | data: User | undefined; 10 | isLoading: boolean; 11 | } 12 | 13 | export const Home = (props: IHomeProps) => { 14 | const { data, isLoading } = props; 15 | return ( 16 | 17 | 18 | {isLoading ? ( 19 | 20 | 21 | 22 | {i18n.t(LocalizationKey.LOADING)} 23 | 24 | 25 | ) : ( 26 | <> 27 | {i18n.t(LocalizationKey.HOME)} 28 | 29 | {data?.username} 30 | 31 | 32 | )} 33 | 34 | ); 35 | }; 36 | 37 | const styles = StyleSheet.create({ 38 | container: { 39 | flex: 1, 40 | backgroundColor: "#fff", 41 | alignItems: "center", 42 | justifyContent: "center", 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/Screens/Home/HomeContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Home } from "./Home"; 2 | import React, { useState, useEffect } from "react"; 3 | import { useLazyGetUserQuery } from "@/Services"; 4 | 5 | export const HomeContainer = () => { 6 | const [userId, setUserId] = useState("9"); 7 | 8 | const [fetchOne, { data, isSuccess, isLoading, isFetching, error }] = 9 | useLazyGetUserQuery(); 10 | 11 | useEffect(() => { 12 | fetchOne(userId); 13 | }, [fetchOne, userId]); 14 | 15 | return ; 16 | }; 17 | -------------------------------------------------------------------------------- /src/Screens/Home/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./HomeContainer"; 2 | -------------------------------------------------------------------------------- /src/Screens/Welcome/Welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { i18n, LocalizationKey } from "@/Localization"; 3 | import { View, Text, StyleSheet } from "react-native"; 4 | import { StatusBar } from "expo-status-bar"; 5 | import { Button } from "native-base"; 6 | import { RootScreens } from ".."; 7 | 8 | export const Welcome = (props: { 9 | onNavigate: (string: RootScreens) => void; 10 | }) => { 11 | return ( 12 | 13 | {i18n.t(LocalizationKey.WELCOME)} 14 | 15 | 18 | 19 | ); 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | flex: 1, 25 | backgroundColor: "#fff", 26 | alignItems: "center", 27 | justifyContent: "center", 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/Screens/Welcome/WelcomeContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Welcome } from "./Welcome"; 3 | import { NativeStackScreenProps } from "@react-navigation/native-stack"; 4 | import { RootStackParamList } from "@/Navigation"; 5 | import { RootScreens } from ".."; 6 | 7 | type WelcomeScreenNavigatorProps = NativeStackScreenProps< 8 | RootStackParamList, 9 | RootScreens.WELCOME 10 | >; 11 | 12 | export const WelcomeContainer = ({ 13 | navigation, 14 | }: WelcomeScreenNavigatorProps) => { 15 | const onNavigate = (screen: RootScreens) => { 16 | navigation.navigate(screen); 17 | }; 18 | 19 | return ; 20 | }; 21 | -------------------------------------------------------------------------------- /src/Screens/Welcome/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./WelcomeContainer"; 2 | -------------------------------------------------------------------------------- /src/Screens/index.ts: -------------------------------------------------------------------------------- 1 | export enum RootScreens { 2 | MAIN = "Main", 3 | WELCOME = "Welcome", 4 | } 5 | -------------------------------------------------------------------------------- /src/Services/base.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "@/Config"; 2 | import { BaseQueryApi } from "@reduxjs/toolkit/dist/query/baseQueryTypes"; 3 | import { 4 | createApi, 5 | FetchArgs, 6 | fetchBaseQuery, 7 | } from "@reduxjs/toolkit/query/react"; 8 | 9 | const baseQuery = fetchBaseQuery({ baseUrl: Config.API_URL }); 10 | 11 | const baseQueryWithInterceptor = async ( 12 | args: string | FetchArgs, 13 | api: BaseQueryApi, 14 | extraOptions: {} 15 | ) => { 16 | const result = await baseQuery(args, api, extraOptions); 17 | if (result.error && result.error.status === 401) { 18 | // here you can deal with 401 error 19 | } 20 | return result; 21 | }; 22 | 23 | export const API = createApi({ 24 | baseQuery: baseQueryWithInterceptor, 25 | endpoints: () => ({}), 26 | }); 27 | -------------------------------------------------------------------------------- /src/Services/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./base"; 2 | export * from "./users"; 3 | -------------------------------------------------------------------------------- /src/Services/users/index.ts: -------------------------------------------------------------------------------- 1 | import { API } from "../base"; 2 | 3 | export interface Geo { 4 | lat: string; 5 | lng: string; 6 | } 7 | 8 | export interface Address { 9 | city: string; 10 | geo: Geo; 11 | street: string; 12 | suite: string; 13 | zipcode: string; 14 | } 15 | 16 | export interface Company { 17 | bs: string; 18 | catchPhrase: string; 19 | name: string; 20 | } 21 | 22 | export interface User { 23 | address: Address; 24 | company: Company; 25 | email: string; 26 | id: number; 27 | name: string; 28 | phone: string; 29 | username: string; 30 | website: string; 31 | } 32 | 33 | const userApi = API.injectEndpoints({ 34 | endpoints: (build) => ({ 35 | getUser: build.query({ 36 | query: (id) => `users/${id}`, 37 | }), 38 | }), 39 | overrideExisting: true, 40 | }); 41 | 42 | export const { useLazyGetUserQuery } = userApi; 43 | -------------------------------------------------------------------------------- /src/Store/index.ts: -------------------------------------------------------------------------------- 1 | import { API } from "@/Services/base"; 2 | import AsyncStorage from "@react-native-async-storage/async-storage"; 3 | import { configureStore, combineReducers } from "@reduxjs/toolkit"; 4 | import { setupListeners } from "@reduxjs/toolkit/query"; 5 | import { 6 | persistReducer, 7 | persistStore, 8 | FLUSH, 9 | REHYDRATE, 10 | PAUSE, 11 | PERSIST, 12 | PURGE, 13 | REGISTER, 14 | } from "redux-persist"; 15 | import { homeReducers, themeReducers } from "./reducers"; 16 | 17 | const reducers = combineReducers({ 18 | api: API.reducer, 19 | theme: themeReducers, 20 | home: homeReducers, 21 | }); 22 | 23 | const persistConfig = { 24 | key: "root", 25 | storage: AsyncStorage, 26 | whitelist: ["theme"], 27 | }; 28 | 29 | const persistedReducer = persistReducer(persistConfig, reducers); 30 | 31 | const store = configureStore({ 32 | reducer: persistedReducer, 33 | middleware: (getDefaultMiddleware) => { 34 | const middlewares = getDefaultMiddleware({ 35 | serializableCheck: { 36 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 37 | }, 38 | }).concat(API.middleware); 39 | 40 | // if (__DEV__ && !process.env.JEST_WORKER_ID) { 41 | // const createDebugger = require("redux-flipper").default; 42 | // middlewares.push(createDebugger()); 43 | // } 44 | 45 | return middlewares; 46 | }, 47 | }); 48 | 49 | const persistor = persistStore(store); 50 | 51 | setupListeners(store.dispatch); 52 | 53 | export { store, persistor }; 54 | -------------------------------------------------------------------------------- /src/Store/reducers/home.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const slice = createSlice({ 4 | name: "home", 5 | initialState: { theme: null, darkMode: null }, 6 | reducers: { 7 | actionA: (state, { payload: { theme, darkMode } }) => { 8 | if (typeof theme !== "undefined") { 9 | state.theme = theme; 10 | } 11 | if (typeof darkMode !== "undefined") { 12 | state.darkMode = darkMode; 13 | } 14 | }, 15 | actionB: (state, { payload: { theme, darkMode } }) => { 16 | if (!state.theme) { 17 | state.theme = theme; 18 | state.darkMode = darkMode; 19 | } 20 | }, 21 | }, 22 | }); 23 | 24 | export const { actionA, actionB } = slice.actions; 25 | 26 | export const homeReducers = slice.reducer; 27 | -------------------------------------------------------------------------------- /src/Store/reducers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./home"; 2 | export * from "./theme"; 3 | -------------------------------------------------------------------------------- /src/Store/reducers/theme.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const slice = createSlice({ 4 | name: "theme", 5 | initialState: { theme: null, darkMode: null }, 6 | reducers: { 7 | changeTheme: (state, { payload: { theme, darkMode } }) => { 8 | if (typeof theme !== "undefined") { 9 | state.theme = theme; 10 | } 11 | if (typeof darkMode !== "undefined") { 12 | state.darkMode = darkMode; 13 | } 14 | }, 15 | setDefaultTheme: (state, { payload: { theme, darkMode } }) => { 16 | if (!state.theme) { 17 | state.theme = theme; 18 | state.darkMode = darkMode; 19 | } 20 | }, 21 | }, 22 | }); 23 | 24 | export const { changeTheme, setDefaultTheme } = slice.actions; 25 | 26 | export const themeReducers = slice.reducer; 27 | -------------------------------------------------------------------------------- /src/Theme/Variables.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the application's variables. 3 | * 4 | * Define color, sizes, etc. here instead of duplicating them throughout the components. 5 | * That allows to change them more easily later on. 6 | */ 7 | 8 | /** 9 | * Colors 10 | */ 11 | export enum Colors { 12 | TRANSPARENT = "rgba(0,0,0,0)", 13 | INPUT_BACKGROUND = "#FFFFFF", 14 | WHITE = "#ffffff", 15 | TEXT = "#212529", 16 | PRIMARY = "#E14032", 17 | SUCCESS = "#28a745", 18 | ERROR = "#dc3545", 19 | } 20 | 21 | export enum NavigationColors { 22 | PRIMARY = Colors.PRIMARY, 23 | } 24 | 25 | /** 26 | * FontSize 27 | */ 28 | export enum FontSize { 29 | SMALL = 16, 30 | REGULAR = 20, 31 | LARGE = 40, 32 | } 33 | 34 | /** 35 | * Metrics Sizes 36 | */ 37 | const tiny = 5; // 10 38 | const small = tiny * 2; // 10 39 | const regular = tiny * 3; // 15 40 | const large = regular * 2; // 30 41 | 42 | export enum MetricsSizes { 43 | TINY = tiny, 44 | SMALL = small, 45 | REGULAR = regular, 46 | LARGE = large, 47 | } 48 | -------------------------------------------------------------------------------- /src/Theme/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoanglehaithanh/react-native-expo-redux-template/67cc60a3aeed3658e8475794daf88bb7ed626ed7/src/Theme/index.ts -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as Localization from "expo-localization"; 3 | import { i18n, Language } from "@/Localization"; 4 | import { NativeBaseProvider } from "native-base"; 5 | import { store, persistor } from "@/Store"; 6 | import { Provider } from "react-redux"; 7 | import { PersistGate } from "redux-persist/integration/react"; 8 | import { ApplicationNavigator } from "./Navigation"; 9 | 10 | i18n.locale = Localization.locale; 11 | i18n.enableFallback = true; 12 | i18n.defaultLocale = Language.ENGLISH; 13 | 14 | export default function App() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["src/*"] 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------