├── .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 | [](https://www.npmjs.com/package/react-native-expo-redux-template)
3 | [](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 |
--------------------------------------------------------------------------------