├── .gitattributes
├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .yarn
└── releases
│ └── yarn-4.8.1.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── eslint.config.mts
├── package.json
├── packages
├── cra-template-redux-typescript
│ ├── README.md
│ ├── package.json
│ ├── template.json
│ └── template
│ │ ├── .prettierrc.json
│ │ ├── README.md
│ │ ├── eslint.config.mjs
│ │ ├── gitignore
│ │ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ │ ├── src
│ │ ├── App.css
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── app
│ │ │ ├── createAppSlice.ts
│ │ │ ├── hooks.ts
│ │ │ └── store.ts
│ │ ├── features
│ │ │ ├── counter
│ │ │ │ ├── Counter.module.css
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── counterAPI.ts
│ │ │ │ ├── counterSlice.test.ts
│ │ │ │ └── counterSlice.ts
│ │ │ └── quotes
│ │ │ │ ├── Quotes.module.css
│ │ │ │ ├── Quotes.tsx
│ │ │ │ └── quotesApiSlice.ts
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── logo.svg
│ │ ├── react-app-env.d.ts
│ │ ├── reportWebVitals.ts
│ │ ├── setupTests.ts
│ │ └── utils
│ │ │ └── test-utils.tsx
│ │ └── tsconfig.json
├── cra-template-redux
│ ├── README.md
│ ├── package.json
│ ├── template.json
│ └── template
│ │ ├── .prettierrc.json
│ │ ├── README.md
│ │ ├── eslint.config.mjs
│ │ ├── gitignore
│ │ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ │ └── src
│ │ ├── App.css
│ │ ├── App.jsx
│ │ ├── App.test.jsx
│ │ ├── app
│ │ ├── createAppSlice.js
│ │ └── store.js
│ │ ├── features
│ │ ├── counter
│ │ │ ├── Counter.jsx
│ │ │ ├── Counter.module.css
│ │ │ ├── counterAPI.js
│ │ │ ├── counterSlice.js
│ │ │ └── counterSlice.test.js
│ │ └── quotes
│ │ │ ├── Quotes.jsx
│ │ │ ├── Quotes.module.css
│ │ │ └── quotesApiSlice.js
│ │ ├── index.css
│ │ ├── index.jsx
│ │ ├── logo.svg
│ │ ├── reportWebVitals.js
│ │ ├── setupTests.js
│ │ └── utils
│ │ └── test-utils.jsx
├── expo-template-redux-typescript
│ ├── .gitignore
│ ├── .prettierrc.json
│ ├── App.tsx
│ ├── README.md
│ ├── app.json
│ ├── assets
│ │ ├── adaptive-icon.png
│ │ ├── favicon.png
│ │ ├── icon.png
│ │ └── splash-icon.png
│ ├── babel.config.js
│ ├── eslint.config.mjs
│ ├── gitignore
│ ├── globals.d.ts
│ ├── index.ts
│ ├── jest-setup.ts
│ ├── jest.config.ts
│ ├── metro.base.config.ts
│ ├── metro.config.js
│ ├── package.json
│ ├── src
│ │ ├── Main.test.tsx
│ │ ├── Main.tsx
│ │ ├── app
│ │ │ ├── createAppSlice.ts
│ │ │ ├── hooks.ts
│ │ │ └── store.ts
│ │ ├── components
│ │ │ ├── AsyncButton.tsx
│ │ │ ├── ExternalLink.tsx
│ │ │ ├── ExternalLinks.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── LearnReduxLinks.tsx
│ │ │ ├── Section.tsx
│ │ │ └── logo.gif
│ │ ├── constants
│ │ │ └── Colors.ts
│ │ ├── features
│ │ │ ├── counter
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── counterAPI.ts
│ │ │ │ ├── counterSlice.test.ts
│ │ │ │ └── counterSlice.ts
│ │ │ └── quotes
│ │ │ │ ├── Quotes.tsx
│ │ │ │ └── quotesApiSlice.ts
│ │ └── utils
│ │ │ └── test-utils.tsx
│ └── tsconfig.json
├── react-native-template-redux-typescript
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── template.config.js
│ └── template
│ │ ├── .bundle
│ │ └── config
│ │ ├── .gitignore
│ │ ├── .prettierrc.json
│ │ ├── .watchmanconfig
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── Gemfile
│ │ ├── README.md
│ │ ├── android
│ │ ├── app
│ │ │ ├── build.gradle
│ │ │ ├── debug.keystore
│ │ │ ├── proguard-rules.pro
│ │ │ └── src
│ │ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ │ └── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── ReactNativeReduxTemplate
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ └── MainApplication.kt
│ │ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── rn_edit_text_material.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ └── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ └── settings.gradle
│ │ ├── app.json
│ │ ├── babel.config.js
│ │ ├── eslint.config.mjs
│ │ ├── globals.d.ts
│ │ ├── index.js
│ │ ├── ios
│ │ ├── .xcode.env
│ │ ├── Podfile
│ │ ├── ReactNativeReduxTemplate.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── ReactNativeReduxTemplate.xcscheme
│ │ ├── ReactNativeReduxTemplate
│ │ │ ├── AppDelegate.h
│ │ │ ├── AppDelegate.mm
│ │ │ ├── Images.xcassets
│ │ │ │ ├── AppIcon.appiconset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Info.plist
│ │ │ ├── LaunchScreen.storyboard
│ │ │ ├── PrivacyInfo.xcprivacy
│ │ │ └── main.m
│ │ └── ReactNativeReduxTemplateTests
│ │ │ ├── Info.plist
│ │ │ └── ReactNativeReduxTemplateTests.m
│ │ ├── jest-setup.ts
│ │ ├── jest.config.ts
│ │ ├── metro.config.js
│ │ ├── package.json
│ │ ├── react-native.config.mjs
│ │ ├── src
│ │ ├── app
│ │ │ ├── createAppSlice.ts
│ │ │ ├── hooks.ts
│ │ │ └── store.ts
│ │ ├── components
│ │ │ ├── AsyncButton.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── LearnReduxLinks.tsx
│ │ │ ├── Section.tsx
│ │ │ └── logo.gif
│ │ ├── constants
│ │ │ └── TypedColors.ts
│ │ ├── features
│ │ │ ├── counter
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── counterAPI.ts
│ │ │ │ ├── counterSlice.test.ts
│ │ │ │ └── counterSlice.ts
│ │ │ └── quotes
│ │ │ │ ├── Quotes.tsx
│ │ │ │ └── quotesApiSlice.ts
│ │ └── utils
│ │ │ └── test-utils.tsx
│ │ └── tsconfig.json
├── rtk-app-structure-example
│ ├── .gitignore
│ ├── .prettierrc.json
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── app
│ │ │ ├── hooks.ts
│ │ │ └── store.ts
│ │ ├── features
│ │ │ └── counter
│ │ │ │ ├── Counter.module.css
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── counterAPI.ts
│ │ │ │ └── counterSlice.ts
│ │ ├── index.css
│ │ ├── logo.svg
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.mts
└── vite-template-redux
│ ├── .gitignore
│ ├── .prettierrc.json
│ ├── README.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── app
│ │ ├── createAppSlice.ts
│ │ ├── hooks.ts
│ │ └── store.ts
│ ├── features
│ │ ├── counter
│ │ │ ├── Counter.module.css
│ │ │ ├── Counter.tsx
│ │ │ ├── counterAPI.ts
│ │ │ ├── counterSlice.test.ts
│ │ │ └── counterSlice.ts
│ │ └── quotes
│ │ │ ├── Quotes.module.css
│ │ │ ├── Quotes.tsx
│ │ │ └── quotesApiSlice.ts
│ ├── index.css
│ ├── logo.svg
│ ├── main.tsx
│ ├── setupTests.ts
│ ├── utils
│ │ └── test-utils.tsx
│ └── vite-env.d.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── prettier.config.mjs
├── scripts
└── mockTemplates.mts
├── tsconfig.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request, workflow_dispatch]
4 |
5 | defaults:
6 | run:
7 | shell: bash
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 | name: Test and lint ${{ matrix.packages }} on Node.js ${{ matrix.node }} and ${{ matrix.os }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | node: [22.x]
17 | os: [ubuntu-latest, windows-latest]
18 | packages:
19 | [
20 | "cra-template-redux",
21 | "cra-template-redux-typescript",
22 | "expo-template-redux-typescript",
23 | "react-native-template-redux-typescript",
24 | "vite-template-redux",
25 | ]
26 |
27 | steps:
28 | - name: Checkout repository for ${{ matrix.packages }} on ${{ matrix.node }} and ${{ matrix.os }}
29 | if: ${{ github.event_name == 'pull_request' }}
30 | uses: actions/checkout@v4
31 | with:
32 | ref: ${{ github.event.pull_request.head.ref }}
33 | repository: ${{ github.event.pull_request.head.repo.full_name }}
34 |
35 | - name: Checkout repository for ${{ matrix.packages }} on ${{ matrix.node }} and ${{ matrix.os }}
36 | if: ${{ github.event_name != 'pull_request' }}
37 | uses: actions/checkout@v4
38 |
39 | - name: Setup Node.js ${{ matrix.node }} for ${{ matrix.packages }} on ${{ matrix.os }}
40 | uses: actions/setup-node@v4
41 | with:
42 | node-version: ${{ matrix.node }}
43 | check-latest: true
44 |
45 | - name: Set up environment variables for ${{ matrix.packages }} on ${{ matrix.node }} and ${{ matrix.os }}
46 | if: ${{ matrix.os == 'windows-latest' }}
47 | run: echo "TEMP=$USERPROFILE\AppData\Local\Temp" >> $GITHUB_ENV
48 |
49 | - name: Set up JDK for React Native build for ${{ matrix.packages }} on ${{ matrix.node }} and ${{ matrix.os }}
50 | if: matrix.packages == 'react-native-template-redux-typescript' || matrix.packages == 'expo-template-redux-typescript'
51 | uses: actions/setup-java@v4
52 | with:
53 | java-version: "21.x"
54 | distribution: "temurin"
55 | cache: "gradle"
56 |
57 | - name: Mock the templates for ${{ matrix.packages }} on ${{ matrix.node }} and ${{ matrix.os }}
58 | run: npx -y tsx@latest scripts/mockTemplates.mts ${{ matrix.packages }}
59 |
60 | - name: Did we fail?
61 | if: failure()
62 | working-directory: packages/${{ matrix.packages }}
63 | run: ls -R
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | node_modules
4 | dist*/
5 | lib
6 | es
7 |
8 | .yalc/
9 | yalc.lock
10 |
11 | .idea/
12 | .vscode/
13 | temp/
14 | .tmp-projections
15 | build/
16 | .rts2*
17 | coverage/
18 |
19 | typesversions
20 | .cache
21 | .yarnrc
22 | .yarn/*
23 | !.yarn/patches
24 | !.yarn/releases
25 | !.yarn/plugins
26 | !.yarn/sdks
27 | !.yarn/versions
28 | .pnp.*
29 | *.tgz
30 |
31 | tsconfig.vitest-temp.json
32 |
33 | # Ignore template lock files
34 | /packages/**/package-lock.json
35 | /packages/**/yarn.lock
36 |
37 | .eslintcache
38 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | compressionLevel: mixed
2 |
3 | enableGlobalCache: false
4 |
5 | enableTransparentWorkspaces: false
6 |
7 | nodeLinker: node-modules
8 |
9 | yarnPath: .yarn/releases/yarn-4.8.1.cjs
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Redux
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redux Project Templates
2 |
3 | This monorepo contains the official Redux templates for Vite, Create-React-App, and (eventually) more.
4 |
5 | For installation and setup instructions, see the README file in each project template folder under `./packages/`.
6 |
7 | Currently, this repo contains these templates:
8 |
9 | - `vite-template-redux`: Vite, with TypeScript
10 | - `cra-template-redux-typescript`: Create-React-App, with TypeScript
11 | - `cra-template-redux`: Create-React-App, with JavaScript
12 | - `expo-template-redux`: Expo, with TypeScript
13 | - `rtk-app-structure-example`: A standalone example of a Redux Toolkit
14 |
--------------------------------------------------------------------------------
/eslint.config.mts:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import prettierConfig from "eslint-config-prettier/flat"
3 | import type { ConfigArray } from "typescript-eslint"
4 | import { config, configs } from "typescript-eslint"
5 |
6 | const eslintConfig: ConfigArray = config(
7 | {
8 | name: "global-ignores",
9 | ignores: [
10 | "**/*.snap",
11 | "**/dist/",
12 | "**/.yalc/",
13 | "**/build/",
14 | "**/temp/",
15 | "**/.temp/",
16 | "**/.tmp/",
17 | "**/.yarn/",
18 | "**/coverage/",
19 | "packages/rtk-app-structure-example",
20 | "packages/vite-template-redux",
21 | "packages/react-native-template-redux-typescript/template",
22 | "packages/expo-template-redux-typescript",
23 | "packages/cra-template-redux/template",
24 | "packages/cra-template-redux-typescript/template",
25 | ],
26 | },
27 | {
28 | name: `${js.meta.name}/recommended`,
29 | ...js.configs.recommended,
30 | },
31 | configs.strictTypeChecked,
32 | configs.stylisticTypeChecked,
33 | {
34 | name: "main",
35 | linterOptions: {
36 | reportUnusedDisableDirectives: 2,
37 | },
38 | languageOptions: {
39 | parserOptions: {
40 | projectService: true,
41 | tsconfigRootDir: import.meta.dirname,
42 | },
43 | },
44 | rules: {
45 | "no-undef": [0],
46 | "@typescript-eslint/consistent-type-definitions": [2, "type"],
47 | "@typescript-eslint/consistent-type-imports": [
48 | 2,
49 | {
50 | prefer: "type-imports",
51 | fixStyle: "separate-type-imports",
52 | disallowTypeAnnotations: true,
53 | },
54 | ],
55 | },
56 | },
57 |
58 | prettierConfig,
59 | )
60 |
61 | export default eslintConfig
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-templates",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "homepage": "https://github.com/reduxjs/redux-templates#readme",
7 | "bugs": {
8 | "url": "https://github.com/reduxjs/redux-templates/issues"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/reduxjs/redux-templates.git",
13 | "directory": "."
14 | },
15 | "workspaces": [
16 | "packages/*"
17 | ],
18 | "scripts": {
19 | "format": "prettier --config=$INIT_CWD/prettier.config.mjs --write $INIT_CWD",
20 | "format-check": "prettier --config=$INIT_CWD/prettier.config.mjs --check $INIT_CWD",
21 | "lint": "eslint --config=$INIT_CWD/eslint.config.mts $INIT_CWD",
22 | "lint-fix": "eslint --config=$INIT_CWD/eslint.config.mts $INIT_CWD --fix",
23 | "type-check": "tsc -p $INIT_CWD/tsconfig.json --noEmit"
24 | },
25 | "devDependencies": {
26 | "@eslint/js": "^9.23.0",
27 | "@types/node": "^22.14.0",
28 | "eslint": "^9.23.0",
29 | "eslint-config-prettier": "^10.1.1",
30 | "jiti": "^2.4.2",
31 | "prettier": "^3.5.3",
32 | "rimraf": "^6.0.1",
33 | "tsx": "^4.19.3",
34 | "typescript": "^5.8.2",
35 | "typescript-eslint": "^8.29.0"
36 | },
37 | "packageManager": "yarn@4.8.1"
38 | }
39 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/README.md:
--------------------------------------------------------------------------------
1 | # cra-template-redux-typescript
2 |
3 | 
4 |
5 | The official Redux+TS template for [Create React App](https://github.com/facebook/create-react-app).
6 |
7 | To use this template within your project, add `--template redux-typescript` when creating a new app.
8 |
9 | For example:
10 |
11 | ```sh
12 | npx create-react-app my-app --template redux-typescript
13 |
14 | # or
15 |
16 | yarn create react-app my-app --template redux-typescript
17 | ```
18 |
19 | Cloning this repo pulls down the Redux template only; not a bundled and configured Create React App.
20 |
21 | For more information, please refer to:
22 |
23 | - [Getting Started](https://create-react-app.dev/docs/getting-started) – How to create a new app.
24 | - [User Guide](https://create-react-app.dev) – How to develop apps bootstrapped with Create React App.
25 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-template-redux-typescript",
3 | "version": "2.0.0",
4 | "scripts": {
5 | "format": "prettier --write ./template/src/**/*.{ts,tsx,css}",
6 | "lint": "eslint './template/src/**/*.{ts,tsx}'",
7 | "test": "jest",
8 | "type-check": "tsc"
9 | },
10 | "keywords": [
11 | "react",
12 | "create-react-app",
13 | "template",
14 | "typescript",
15 | "redux",
16 | "reduxjs",
17 | "react-redux"
18 | ],
19 | "description": "The official Redux+TS template for Create React App",
20 | "main": "template.json",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/reduxjs/cra-template-redux-typescript.git",
24 | "directory": "packages/cra-template-redux-typescript"
25 | },
26 | "license": "MIT",
27 | "engines": {
28 | "node": ">=8.10"
29 | },
30 | "homepage": "https://github.com/reduxjs/redux-templates/tree/HEAD/packages/cra-template-redux-typescript#readme",
31 | "bugs": {
32 | "url": "https://github.com/reduxjs/redux-templates/issues"
33 | },
34 | "files": [
35 | "template",
36 | "template.json"
37 | ],
38 | "devDependencies": {
39 | "@babel/core": "^7.26.10",
40 | "@babel/preset-env": "^7.26.9",
41 | "@babel/preset-react": "^7.26.3",
42 | "@reduxjs/toolkit": "^2.6.1",
43 | "@testing-library/dom": "^10.4.0",
44 | "@testing-library/jest-dom": "^6.6.3",
45 | "@testing-library/react": "^16.3.0",
46 | "@testing-library/user-event": "^14.6.1",
47 | "@types/jest": "^29.5.14",
48 | "@types/node": "^22.14.0",
49 | "@types/react": "^19.1.0",
50 | "@types/react-dom": "^19.1.1",
51 | "babel-eslint": "^10.1.0",
52 | "babel-jest": "^29.7.0",
53 | "eslint": "^9.23.0",
54 | "eslint-config-react-app": "^7.0.1",
55 | "eslint-plugin-import": "^2.31.0",
56 | "eslint-plugin-jsx-a11y": "^6.10.2",
57 | "eslint-plugin-prettier": "^5.2.6",
58 | "eslint-plugin-react": "^7.37.4",
59 | "eslint-plugin-react-hooks": "^5.2.0",
60 | "jest": "^29.7.0",
61 | "prettier": "^3.5.3",
62 | "react": "^19.1.0",
63 | "react-dom": "^19.1.0",
64 | "react-redux": "^9.2.0",
65 | "react-scripts": "^5.0.1",
66 | "react-test-renderer": "^19.1.0",
67 | "typescript": "^5.8.2",
68 | "web-vitals": "^4.2.4"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": {
3 | "dependencies": {
4 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
5 | "@eslint/js": "^9.23.0",
6 | "@reduxjs/toolkit": "^2.6.1",
7 | "@testing-library/jest-dom": "^6.6.3",
8 | "@testing-library/react": "^16.2.0",
9 | "@testing-library/user-event": "^14.6.1",
10 | "@types/jest": "^29.5.14",
11 | "@types/node": "^22.13.14",
12 | "@types/react": "^19.0.12",
13 | "@types/react-dom": "^19.0.4",
14 | "eslint": "^9.23.0",
15 | "eslint-config-prettier": "^10.1.1",
16 | "eslint-plugin-jest": "^28.11.0",
17 | "eslint-plugin-react": "^7.37.4",
18 | "eslint-plugin-react-hooks": "^5.2.0",
19 | "globals": "^16.0.0",
20 | "prettier": "^3.5.3",
21 | "react-redux": "^9.2.0",
22 | "typescript": "^4.9.5",
23 | "typescript-eslint": "^8.28.0",
24 | "web-vitals": "^4.2.4"
25 | },
26 | "eslintConfig": {},
27 | "scripts": {
28 | "format": "prettier --write .",
29 | "format:check": "prettier --check .",
30 | "lint": "eslint .",
31 | "lint:fix": "eslint --fix .",
32 | "type-check": "tsc"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) TS template.
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import prettierConfig from "eslint-config-prettier/flat"
3 | import jestPlugin from "eslint-plugin-jest"
4 | import reactPlugin from "eslint-plugin-react"
5 | import reactHooksPlugin from "eslint-plugin-react-hooks"
6 | import globals from "globals"
7 | import { config, configs } from "typescript-eslint"
8 |
9 | const eslintConfig = config(
10 | {
11 | name: "global-ignores",
12 | ignores: [
13 | "**/*.snap",
14 | "**/dist/",
15 | "**/.yalc/",
16 | "**/build/",
17 | "**/temp/",
18 | "**/.temp/",
19 | "**/.tmp/",
20 | "**/.yarn/",
21 | "**/coverage/",
22 | ],
23 | },
24 | {
25 | name: `${js.meta.name}/recommended`,
26 | ...js.configs.recommended,
27 | },
28 | configs.strictTypeChecked,
29 | configs.stylisticTypeChecked,
30 | {
31 | name: `${jestPlugin.meta.name}/recommended`,
32 | ...jestPlugin.configs["flat/recommended"],
33 | },
34 | {
35 | name: "eslint-plugin-react/jsx-runtime",
36 | ...reactPlugin.configs.flat["jsx-runtime"],
37 | },
38 | reactHooksPlugin.configs["recommended-latest"],
39 | {
40 | name: "main",
41 | linterOptions: {
42 | reportUnusedDisableDirectives: 2,
43 | },
44 | languageOptions: {
45 | ecmaVersion: 2020,
46 | globals: globals.browser,
47 | parserOptions: {
48 | projectService: {
49 | allowDefaultProject: ["*.?(c|m)[jt]s?(x)"],
50 | },
51 | tsconfigRootDir: import.meta.dirname,
52 | },
53 | },
54 | rules: {
55 | "no-undef": [0],
56 | "no-restricted-imports": [
57 | 2,
58 | {
59 | paths: [
60 | {
61 | name: "react-redux",
62 | importNames: ["useSelector", "useStore", "useDispatch"],
63 | message:
64 | "Please use pre-typed versions from `src/app/hooks.ts` instead.",
65 | },
66 | ],
67 | },
68 | ],
69 | "@typescript-eslint/consistent-type-definitions": [2, "type"],
70 | "@typescript-eslint/consistent-type-imports": [
71 | 2,
72 | {
73 | prefer: "type-imports",
74 | fixStyle: "separate-type-imports",
75 | disallowTypeAnnotations: true,
76 | },
77 | ],
78 | },
79 | },
80 |
81 | prettierConfig,
82 | )
83 |
84 | export default eslintConfig
85 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .yalc/
26 | yalc.lock
27 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux-typescript/template/public/favicon.ico
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React Redux App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux-typescript/template/public/logo192.png
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux-typescript/template/public/logo512.png
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-float infinite 3s ease-in-out;
13 | }
14 | }
15 |
16 | .App-header {
17 | min-height: 100vh;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | justify-content: center;
22 | font-size: calc(10px + 2vmin);
23 | }
24 |
25 | .App-link {
26 | color: rgb(112, 76, 182);
27 | }
28 |
29 | @keyframes App-logo-float {
30 | 0% {
31 | transform: translateY(0);
32 | }
33 | 50% {
34 | transform: translateY(10px);
35 | }
36 | 100% {
37 | transform: translateY(0px);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css"
2 | import { Counter } from "./features/counter/Counter"
3 | import { Quotes } from "./features/quotes/Quotes"
4 | import logo from "./logo.svg"
5 |
6 | export const App = () => (
7 |
64 | )
65 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/app/createAppSlice.ts:
--------------------------------------------------------------------------------
1 | import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2 |
3 | // `buildCreateSlice` allows us to create a slice with async thunks.
4 | export const createAppSlice = buildCreateSlice({
5 | creators: { asyncThunk: asyncThunkCreator },
6 | })
7 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | // These imports are restricted elsewhere to ensure consistent
3 | // usage of typed hooks throughout the application.
4 | // We disable the ESLint rule here because this is the designated place
5 | // for importing and re-exporting the typed versions of hooks.
6 | /* eslint-disable no-restricted-imports */
7 | import { useDispatch, useSelector } from "react-redux"
8 | import type { AppDispatch, RootState } from "./store"
9 |
10 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
11 | export const useAppDispatch = useDispatch.withTypes()
12 | export const useAppSelector = useSelector.withTypes()
13 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import type { Action, ThunkAction } from "@reduxjs/toolkit"
2 | import { combineSlices, configureStore } from "@reduxjs/toolkit"
3 | import { setupListeners } from "@reduxjs/toolkit/query"
4 | import { counterSlice } from "../features/counter/counterSlice"
5 | import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
6 |
7 | // `combineSlices` automatically combines the reducers using
8 | // their `reducerPath`s, therefore we no longer need to call `combineReducers`.
9 | const rootReducer = combineSlices(counterSlice, quotesApiSlice)
10 | // Infer the `RootState` type from the root reducer
11 | export type RootState = ReturnType
12 |
13 | // The store setup is wrapped in `makeStore` to allow reuse
14 | // when setting up tests that need the same store config
15 | export const makeStore = (preloadedState?: Partial) => {
16 | const store = configureStore({
17 | reducer: rootReducer,
18 | // Adding the api middleware enables caching, invalidation, polling,
19 | // and other useful features of `rtk-query`.
20 | middleware: getDefaultMiddleware => {
21 | return getDefaultMiddleware().concat(quotesApiSlice.middleware)
22 | },
23 | preloadedState,
24 | })
25 | // configure listeners using the provided defaults
26 | // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
27 | setupListeners(store.dispatch)
28 | return store
29 | }
30 |
31 | export const store = makeStore()
32 |
33 | // Infer the type of `store`
34 | export type AppStore = typeof store
35 | // Infer the `AppDispatch` type from the store itself
36 | export type AppDispatch = AppStore["dispatch"]
37 | export type AppThunk = ThunkAction<
38 | ThunkReturnType,
39 | RootState,
40 | unknown,
41 | Action
42 | >
43 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/counter/Counter.module.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 | .row > button {
8 | margin-left: 4px;
9 | margin-right: 8px;
10 | }
11 |
12 | .row:not(:last-child) {
13 | margin-bottom: 16px;
14 | }
15 |
16 | .value {
17 | font-size: 78px;
18 | padding-left: 16px;
19 | padding-right: 16px;
20 | margin-top: 2px;
21 | font-family: "Courier New", Courier, monospace;
22 | }
23 |
24 | .button {
25 | appearance: none;
26 | background: none;
27 | font-size: 32px;
28 | padding-left: 12px;
29 | padding-right: 12px;
30 | outline: none;
31 | border: 2px solid transparent;
32 | color: rgb(112, 76, 182);
33 | padding-bottom: 4px;
34 | cursor: pointer;
35 | background-color: rgba(112, 76, 182, 0.1);
36 | border-radius: 2px;
37 | transition: all 0.15s;
38 | }
39 |
40 | .textbox {
41 | font-size: 32px;
42 | padding: 2px;
43 | width: 64px;
44 | text-align: center;
45 | margin-right: 4px;
46 | }
47 |
48 | .button:hover,
49 | .button:focus {
50 | border: 2px solid rgba(112, 76, 182, 0.4);
51 | }
52 |
53 | .button:active {
54 | background-color: rgba(112, 76, 182, 0.2);
55 | }
56 |
57 | .asyncButton {
58 | composes: button;
59 | position: relative;
60 | }
61 |
62 | .asyncButton:after {
63 | content: "";
64 | background-color: rgba(112, 76, 182, 0.15);
65 | display: block;
66 | position: absolute;
67 | width: 100%;
68 | height: 100%;
69 | left: 0;
70 | top: 0;
71 | opacity: 0;
72 | transition:
73 | width 1s linear,
74 | opacity 0.5s ease 1s;
75 | }
76 |
77 | .asyncButton:active:after {
78 | width: 0%;
79 | opacity: 1;
80 | transition: 0s;
81 | }
82 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { useState } from "react"
3 | import { useAppDispatch, useAppSelector } from "../../app/hooks"
4 | import styles from "./Counter.module.css"
5 | import {
6 | decrement,
7 | increment,
8 | incrementAsync,
9 | incrementByAmount,
10 | incrementIfOdd,
11 | selectCount,
12 | selectStatus,
13 | } from "./counterSlice"
14 |
15 | export const Counter = (): JSX.Element => {
16 | const dispatch = useAppDispatch()
17 | const count = useAppSelector(selectCount)
18 | const status = useAppSelector(selectStatus)
19 | const [incrementAmount, setIncrementAmount] = useState("2")
20 |
21 | const incrementValue = Number(incrementAmount) || 0
22 |
23 | return (
24 |
25 |
26 |
33 |
36 |
43 |
44 |
45 | {
51 | setIncrementAmount(e.target.value)
52 | }}
53 | />
54 |
60 |
69 |
77 |
78 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1): Promise<{ data: number }> =>
3 | new Promise<{ data: number }>(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/counter/counterSlice.test.ts:
--------------------------------------------------------------------------------
1 | import { makeStore } from "../../app/store"
2 | import type { CounterSliceState } from "./counterSlice"
3 | import {
4 | counterSlice,
5 | decrement,
6 | increment,
7 | incrementByAmount,
8 | selectCount,
9 | } from "./counterSlice"
10 |
11 | describe("counter reducer", () => {
12 | const initialState: CounterSliceState = {
13 | value: 3,
14 | status: "idle",
15 | }
16 |
17 | let store = makeStore()
18 |
19 | beforeEach(() => {
20 | store = makeStore({ counter: initialState })
21 | })
22 |
23 | it("should handle initial state", () => {
24 | expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
25 | value: 0,
26 | status: "idle",
27 | })
28 | })
29 |
30 | it("should handle increment", () => {
31 | expect(selectCount(store.getState())).toBe(3)
32 |
33 | store.dispatch(increment())
34 |
35 | expect(selectCount(store.getState())).toBe(4)
36 | })
37 |
38 | it("should handle decrement", () => {
39 | expect(selectCount(store.getState())).toBe(3)
40 |
41 | store.dispatch(decrement())
42 |
43 | expect(selectCount(store.getState())).toBe(2)
44 | })
45 |
46 | it("should handle incrementByAmount", () => {
47 | expect(selectCount(store.getState())).toBe(3)
48 |
49 | store.dispatch(incrementByAmount(2))
50 |
51 | expect(selectCount(store.getState())).toBe(5)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/quotes/Quotes.module.css:
--------------------------------------------------------------------------------
1 | .select {
2 | font-size: 25px;
3 | padding: 5px;
4 | padding-top: 2px;
5 | padding-bottom: 2px;
6 | size: 50;
7 | outline: none;
8 | border: 2px solid transparent;
9 | color: rgb(112, 76, 182);
10 | cursor: pointer;
11 | background-color: rgba(112, 76, 182, 0.1);
12 | border-radius: 5px;
13 | transition: all 0.15s;
14 | }
15 |
16 | .container {
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/quotes/Quotes.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { useState } from "react"
3 | import styles from "./Quotes.module.css"
4 | import { useGetQuotesQuery } from "./quotesApiSlice"
5 |
6 | const options = [5, 10, 20, 30]
7 |
8 | export const Quotes = (): JSX.Element | null => {
9 | const [numberOfQuotes, setNumberOfQuotes] = useState(10)
10 | // Using a query hook automatically fetches data and returns query values
11 | const { data, isError, isLoading, isSuccess } =
12 | useGetQuotesQuery(numberOfQuotes)
13 |
14 | if (isError) {
15 | return (
16 |
17 |
There was an error!!!
18 |
19 | )
20 | }
21 |
22 | if (isLoading) {
23 | return (
24 |
25 |
Loading...
26 |
27 | )
28 | }
29 |
30 | if (isSuccess) {
31 | return (
32 |
33 |
Select the Quantity of Quotes to Fetch:
34 |
47 | {data.quotes.map(({ author, quote, id }) => (
48 |
49 | “{quote}”
50 |
53 |
54 | ))}
55 |
56 | )
57 | }
58 |
59 | return null
60 | }
61 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/features/quotes/quotesApiSlice.ts:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import `createApi`
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
3 |
4 | type Quote = {
5 | id: number
6 | quote: string
7 | author: string
8 | }
9 |
10 | type QuotesApiResponse = {
11 | quotes: Quote[]
12 | total: number
13 | skip: number
14 | limit: number
15 | }
16 |
17 | // Define a service using a base URL and expected endpoints
18 | export const quotesApiSlice = createApi({
19 | baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
20 | reducerPath: "quotesApi",
21 | // Tag types are used for caching and invalidation.
22 | tagTypes: ["Quotes"],
23 | endpoints: build => ({
24 | // Supply generics for the return type (in this case `QuotesApiResponse`)
25 | // and the expected query argument. If there is no argument, use `void`
26 | // for the argument type instead.
27 | getQuotes: build.query({
28 | query: (limit = 10) => `?limit=${limit.toString()}`,
29 | // `providesTags` determines which 'tag' is attached to the
30 | // cached data returned by the query.
31 | providesTags: (_result, _error, id) => [{ type: "Quotes", id }],
32 | }),
33 | }),
34 | })
35 |
36 | // Hooks are auto-generated by RTK-Query
37 | // Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
38 | export const { useGetQuotesQuery } = quotesApiSlice
39 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family:
4 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family:
12 | source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 | import { Provider } from "react-redux"
4 | import { App } from "./App"
5 | import { store } from "./app/store"
6 | import "./index.css"
7 | import reportWebVitals from "./reportWebVitals"
8 |
9 | const container = document.getElementById("root")
10 |
11 | if (container) {
12 | const root = createRoot(container)
13 |
14 | root.render(
15 |
16 |
17 |
18 |
19 | ,
20 | )
21 | } else {
22 | throw new Error(
23 | "Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
24 | )
25 | }
26 |
27 | // If you want to start measuring performance in your app, pass a function
28 | // to log results (for example: reportWebVitals(console.log))
29 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
30 | reportWebVitals()
31 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import type { MetricType } from "web-vitals"
2 |
3 | const reportWebVitals = (onPerfEntry?: (metric: MetricType) => void) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | void import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
6 | onCLS(onPerfEntry)
7 | onINP(onPerfEntry)
8 | onFCP(onPerfEntry)
9 | onLCP(onPerfEntry)
10 | onTTFB(onPerfEntry)
11 | })
12 | }
13 | }
14 |
15 | export default reportWebVitals
16 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom"
6 |
--------------------------------------------------------------------------------
/packages/cra-template-redux-typescript/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/README.md:
--------------------------------------------------------------------------------
1 | # cra-template-redux
2 |
3 | 
4 | [](https://www.npmjs.com/package/cra-template-redux)
5 | [](https://www.npmjs.com/package/cra-template-redux)
6 |
7 | The official Redux+JS template for [Create React App](https://github.com/facebook/create-react-app)
8 |
9 | ## Usage
10 |
11 | To use this template within your project, add `--template redux` when creating a new app.
12 |
13 | For example:
14 |
15 | ```sh
16 | npx create-react-app my-app --template redux
17 |
18 | # or
19 |
20 | yarn create react-app my-app --template redux
21 | ```
22 |
23 | ## TypeScript
24 |
25 | Use [cra-template-redux-typescript](https://github.com/reduxjs/cra-template-redux-typescript), which is based on this template
26 |
27 | ```sh
28 | npx create-react-app my-app --template redux-typescript
29 | ```
30 |
31 | Cloning this repo pulls down the Redux template only; not a bundled and configured Create React App.
32 |
33 | For more information, please refer to:
34 |
35 | - [Getting Started](https://create-react-app.dev/docs/getting-started) – How to create a new app.
36 | - [User Guide](https://create-react-app.dev) – How to develop apps bootstrapped with Create React App.
37 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-template-redux",
3 | "version": "2.0.0",
4 | "scripts": {
5 | "format": "prettier --write ./template/src/**/*.{js,jsx,css}",
6 | "lint": "eslint './template/src/**/*.{js,jsx}'",
7 | "test": "jest"
8 | },
9 | "keywords": [
10 | "react",
11 | "create-react-app",
12 | "template",
13 | "javascript",
14 | "redux",
15 | "reduxjs",
16 | "react-redux"
17 | ],
18 | "description": "The official Redux+JS template for Create React App.",
19 | "main": "template.json",
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/reduxjs/cra-template-redux.git",
23 | "directory": "packages/cra-template-redux"
24 | },
25 | "license": "MIT",
26 | "engines": {
27 | "node": ">=8"
28 | },
29 | "homepage": "https://github.com/reduxjs/redux-templates/tree/HEAD/packages/cra-template-redux#readme",
30 | "bugs": {
31 | "url": "https://github.com/reduxjs/redux-templates/issues"
32 | },
33 | "files": [
34 | "template",
35 | "template.json"
36 | ],
37 | "devDependencies": {
38 | "@babel/core": "^7.26.10",
39 | "@babel/preset-env": "^7.26.9",
40 | "@babel/preset-react": "^7.26.3",
41 | "@reduxjs/toolkit": "^2.6.1",
42 | "@testing-library/dom": "^10.4.0",
43 | "@testing-library/jest-dom": "^6.6.3",
44 | "@testing-library/react": "^16.3.0",
45 | "@testing-library/user-event": "^14.6.1",
46 | "babel-eslint": "^10.1.0",
47 | "babel-jest": "^29.7.0",
48 | "babel-preset-react-app": "^10.1.0",
49 | "eslint": "^9.23.0",
50 | "eslint-config-react-app": "^7.0.1",
51 | "eslint-plugin-import": "^2.31.0",
52 | "eslint-plugin-jsx-a11y": "^6.10.2",
53 | "eslint-plugin-prettier": "^5.2.6",
54 | "eslint-plugin-react": "^7.37.4",
55 | "eslint-plugin-react-hooks": "^5.2.0",
56 | "jest": "^29.7.0",
57 | "prettier": "^3.5.3",
58 | "react": "^19.1.0",
59 | "react-dom": "^19.1.0",
60 | "react-redux": "^9.2.0",
61 | "react-scripts": "^5.0.1",
62 | "react-test-renderer": "^19.1.0",
63 | "web-vitals": "^4.2.4"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": {
3 | "dependencies": {
4 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
5 | "@eslint/js": "^9.23.0",
6 | "@reduxjs/toolkit": "^2.6.1",
7 | "@testing-library/jest-dom": "^6.6.3",
8 | "@testing-library/react": "^16.2.0",
9 | "@testing-library/user-event": "^14.6.1",
10 | "eslint": "^9.23.0",
11 | "eslint-config-prettier": "^10.1.1",
12 | "eslint-plugin-jest": "^28.11.0",
13 | "eslint-plugin-react-hooks": "^5.2.0",
14 | "globals": "^16.0.0",
15 | "prettier": "^3.5.3",
16 | "react-redux": "^9.2.0",
17 | "typescript-eslint": "^8.28.0",
18 | "web-vitals": "^4.2.4"
19 | },
20 | "eslintConfig": {},
21 | "scripts": {
22 | "format": "prettier --write .",
23 | "format:check": "prettier --check .",
24 | "lint": "eslint .",
25 | "lint:fix": "eslint --fix ."
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App and Redux
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template.
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import prettierConfig from "eslint-config-prettier/flat"
3 | import jestPlugin from "eslint-plugin-jest"
4 | import reactPlugin from "eslint-plugin-react"
5 | import reactHooksPlugin from "eslint-plugin-react-hooks"
6 | import globals from "globals"
7 | import { config, configs } from "typescript-eslint"
8 |
9 | const eslintConfig = config(
10 | {
11 | name: "global-ignores",
12 | ignores: [
13 | "**/*.snap",
14 | "**/dist/",
15 | "**/.yalc/",
16 | "**/build/",
17 | "**/temp/",
18 | "**/.temp/",
19 | "**/.tmp/",
20 | "**/.yarn/",
21 | "**/coverage/",
22 | ],
23 | },
24 | {
25 | name: `${js.meta.name}/recommended`,
26 | ...js.configs.recommended,
27 | },
28 | configs.strict,
29 | configs.stylistic,
30 | {
31 | name: `${jestPlugin.meta.name}/recommended`,
32 | ...jestPlugin.configs["flat/recommended"],
33 | },
34 | {
35 | name: "eslint-plugin-react/jsx-runtime",
36 | ...reactPlugin.configs.flat["jsx-runtime"],
37 | },
38 | reactHooksPlugin.configs["recommended-latest"],
39 | {
40 | name: "main",
41 | linterOptions: {
42 | reportUnusedDisableDirectives: 2,
43 | },
44 | files: ["**/*.?(c|m)js?(x)"],
45 | languageOptions: {
46 | ecmaVersion: 2020,
47 | globals: globals.browser,
48 | },
49 | },
50 |
51 | prettierConfig,
52 | )
53 |
54 | export default eslintConfig
55 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .yalc/
26 | yalc.lock
27 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux/template/public/favicon.ico
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React Redux App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux/template/public/logo192.png
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/cra-template-redux/template/public/logo512.png
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-float infinite 3s ease-in-out;
13 | }
14 | }
15 |
16 | .App-header {
17 | min-height: 100vh;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | justify-content: center;
22 | font-size: calc(10px + 2vmin);
23 | }
24 |
25 | .App-link {
26 | color: rgb(112, 76, 182);
27 | }
28 |
29 | @keyframes App-logo-float {
30 | 0% {
31 | transform: translateY(0);
32 | }
33 | 50% {
34 | transform: translateY(10px);
35 | }
36 | 100% {
37 | transform: translateY(0px);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css"
2 | import { Counter } from "./features/counter/Counter"
3 | import { Quotes } from "./features/quotes/Quotes"
4 | import logo from "./logo.svg"
5 |
6 | export const App = () => (
7 |
64 | )
65 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/app/createAppSlice.js:
--------------------------------------------------------------------------------
1 | import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2 |
3 | // `buildCreateSlice` allows us to create a slice with async thunks.
4 | export const createAppSlice = buildCreateSlice({
5 | creators: { asyncThunk: asyncThunkCreator },
6 | })
7 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { combineSlices, configureStore } from "@reduxjs/toolkit"
2 | import { setupListeners } from "@reduxjs/toolkit/query"
3 | import { counterSlice } from "../features/counter/counterSlice"
4 | import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
5 |
6 | // `combineSlices` automatically combines the reducers using
7 | // their `reducerPath`s, therefore we no longer need to call `combineReducers`.
8 | const rootReducer = combineSlices(counterSlice, quotesApiSlice)
9 |
10 | // The store setup is wrapped in `makeStore` to allow reuse
11 | // when setting up tests that need the same store config
12 | export const makeStore = preloadedState => {
13 | const store = configureStore({
14 | reducer: rootReducer,
15 | // Adding the api middleware enables caching, invalidation, polling,
16 | // and other useful features of `rtk-query`.
17 | middleware: getDefaultMiddleware => {
18 | return getDefaultMiddleware().concat(quotesApiSlice.middleware)
19 | },
20 | preloadedState,
21 | })
22 | // configure listeners using the provided defaults
23 | // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
24 | setupListeners(store.dispatch)
25 | return store
26 | }
27 |
28 | export const store = makeStore()
29 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/counter/Counter.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import styles from "./Counter.module.css"
4 | import {
5 | decrement,
6 | increment,
7 | incrementAsync,
8 | incrementByAmount,
9 | incrementIfOdd,
10 | selectCount,
11 | selectStatus,
12 | } from "./counterSlice"
13 |
14 | export const Counter = () => {
15 | const dispatch = useDispatch()
16 | const count = useSelector(selectCount)
17 | const status = useSelector(selectStatus)
18 | const [incrementAmount, setIncrementAmount] = useState("2")
19 |
20 | const incrementValue = Number(incrementAmount) || 0
21 |
22 | return (
23 |
24 |
25 |
32 |
35 |
42 |
43 |
44 | {
50 | setIncrementAmount(e.target.value)
51 | }}
52 | />
53 |
59 |
68 |
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/counter/Counter.module.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 | .row > button {
8 | margin-left: 4px;
9 | margin-right: 8px;
10 | }
11 |
12 | .row:not(:last-child) {
13 | margin-bottom: 16px;
14 | }
15 |
16 | .value {
17 | font-size: 78px;
18 | padding-left: 16px;
19 | padding-right: 16px;
20 | margin-top: 2px;
21 | font-family: "Courier New", Courier, monospace;
22 | }
23 |
24 | .button {
25 | appearance: none;
26 | background: none;
27 | font-size: 32px;
28 | padding-left: 12px;
29 | padding-right: 12px;
30 | outline: none;
31 | border: 2px solid transparent;
32 | color: rgb(112, 76, 182);
33 | padding-bottom: 4px;
34 | cursor: pointer;
35 | background-color: rgba(112, 76, 182, 0.1);
36 | border-radius: 2px;
37 | transition: all 0.15s;
38 | }
39 |
40 | .textbox {
41 | font-size: 32px;
42 | padding: 2px;
43 | width: 64px;
44 | text-align: center;
45 | margin-right: 4px;
46 | }
47 |
48 | .button:hover,
49 | .button:focus {
50 | border: 2px solid rgba(112, 76, 182, 0.4);
51 | }
52 |
53 | .button:active {
54 | background-color: rgba(112, 76, 182, 0.2);
55 | }
56 |
57 | .asyncButton {
58 | composes: button;
59 | position: relative;
60 | }
61 |
62 | .asyncButton:after {
63 | content: "";
64 | background-color: rgba(112, 76, 182, 0.15);
65 | display: block;
66 | position: absolute;
67 | width: 100%;
68 | height: 100%;
69 | left: 0;
70 | top: 0;
71 | opacity: 0;
72 | transition:
73 | width 1s linear,
74 | opacity 0.5s ease 1s;
75 | }
76 |
77 | .asyncButton:active:after {
78 | width: 0%;
79 | opacity: 1;
80 | transition: 0s;
81 | }
82 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/counter/counterAPI.js:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1) =>
3 | new Promise(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/counter/counterSlice.test.js:
--------------------------------------------------------------------------------
1 | import { makeStore } from "../../app/store"
2 | import {
3 | counterSlice,
4 | decrement,
5 | increment,
6 | incrementByAmount,
7 | selectCount,
8 | } from "./counterSlice"
9 |
10 | describe("counter reducer", () => {
11 | const initialState = {
12 | value: 3,
13 | status: "idle",
14 | }
15 |
16 | let store = makeStore()
17 |
18 | beforeEach(() => {
19 | store = makeStore({ counter: initialState })
20 | })
21 |
22 | it("should handle initial state", () => {
23 | expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
24 | value: 0,
25 | status: "idle",
26 | })
27 | })
28 |
29 | it("should handle increment", () => {
30 | expect(selectCount(store.getState())).toBe(3)
31 |
32 | store.dispatch(increment())
33 |
34 | expect(selectCount(store.getState())).toBe(4)
35 | })
36 |
37 | it("should handle decrement", () => {
38 | expect(selectCount(store.getState())).toBe(3)
39 |
40 | store.dispatch(decrement())
41 |
42 | expect(selectCount(store.getState())).toBe(2)
43 | })
44 |
45 | it("should handle incrementByAmount", () => {
46 | expect(selectCount(store.getState())).toBe(3)
47 |
48 | store.dispatch(incrementByAmount(2))
49 |
50 | expect(selectCount(store.getState())).toBe(5)
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/quotes/Quotes.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import styles from "./Quotes.module.css"
3 | import { useGetQuotesQuery } from "./quotesApiSlice"
4 |
5 | const options = [5, 10, 20, 30]
6 |
7 | export const Quotes = () => {
8 | const [numberOfQuotes, setNumberOfQuotes] = useState(10)
9 | // Using a query hook automatically fetches data and returns query values
10 | const { data, isError, isLoading, isSuccess } =
11 | useGetQuotesQuery(numberOfQuotes)
12 |
13 | if (isError) {
14 | return (
15 |
16 |
There was an error!!!
17 |
18 | )
19 | }
20 |
21 | if (isLoading) {
22 | return (
23 |
24 |
Loading...
25 |
26 | )
27 | }
28 |
29 | if (isSuccess) {
30 | return (
31 |
32 |
Select the Quantity of Quotes to Fetch:
33 |
46 | {data.quotes.map(({ author, quote, id }) => (
47 |
48 | “{quote}”
49 |
52 |
53 | ))}
54 |
55 | )
56 | }
57 |
58 | return null
59 | }
60 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/quotes/Quotes.module.css:
--------------------------------------------------------------------------------
1 | .select {
2 | font-size: 25px;
3 | padding: 5px;
4 | padding-top: 2px;
5 | padding-bottom: 2px;
6 | size: 50;
7 | outline: none;
8 | border: 2px solid transparent;
9 | color: rgb(112, 76, 182);
10 | cursor: pointer;
11 | background-color: rgba(112, 76, 182, 0.1);
12 | border-radius: 5px;
13 | transition: all 0.15s;
14 | }
15 |
16 | .container {
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/features/quotes/quotesApiSlice.js:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import `createApi`
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
3 |
4 | // Define a service using a base URL and expected endpoints
5 | export const quotesApiSlice = createApi({
6 | baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
7 | reducerPath: "quotesApi",
8 | // Tag types are used for caching and invalidation.
9 | tagTypes: ["Quotes"],
10 | endpoints: build => ({
11 | // Supply generics for the return type (in this case `QuotesApiResponse`)
12 | // and the expected query argument. If there is no argument, use `void`
13 | // for the argument type instead.
14 | getQuotes: build.query({
15 | query: (limit = 10) => `?limit=${limit.toString()}`,
16 | // `providesTags` determines which 'tag' is attached to the
17 | // cached data returned by the query.
18 | providesTags: (_result, _error, id) => [{ type: "Quotes", id }],
19 | }),
20 | }),
21 | })
22 |
23 | // Hooks are auto-generated by RTK-Query
24 | // Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
25 | export const { useGetQuotesQuery } = quotesApiSlice
26 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family:
4 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family:
12 | source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/index.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 | import { Provider } from "react-redux"
4 | import { App } from "./App"
5 | import { store } from "./app/store"
6 | import "./index.css"
7 | import reportWebVitals from "./reportWebVitals"
8 |
9 | const container = document.getElementById("root")
10 |
11 | if (container) {
12 | const root = createRoot(container)
13 |
14 | root.render(
15 |
16 |
17 |
18 |
19 | ,
20 | )
21 | } else {
22 | throw new Error(
23 | "Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
24 | )
25 | }
26 |
27 | // If you want to start measuring performance in your app, pass a function
28 | // to log results (for example: reportWebVitals(console.log))
29 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
30 | reportWebVitals()
31 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | void import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
4 | onCLS(onPerfEntry)
5 | onINP(onPerfEntry)
6 | onFCP(onPerfEntry)
7 | onLCP(onPerfEntry)
8 | onTTFB(onPerfEntry)
9 | })
10 | }
11 | }
12 |
13 | export default reportWebVitals
14 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom"
6 |
--------------------------------------------------------------------------------
/packages/cra-template-redux/template/src/utils/test-utils.jsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react"
2 | import { userEvent } from "@testing-library/user-event"
3 | import { Provider } from "react-redux"
4 | import { makeStore } from "../app/store"
5 |
6 | /**
7 | * Renders the given React element with Redux Provider and custom store.
8 | * This function is useful for testing components that are connected to the Redux store.
9 | *
10 | * @param ui - The React component or element to render.
11 | * @param extendedRenderOptions - Optional configuration options for rendering. This includes `preloadedState` for initial Redux state and `store` for a specific Redux store instance. Any additional properties are passed to React Testing Library's render function.
12 | * @returns An object containing the Redux store used in the render, User event API for simulating user interactions in tests, and all of React Testing Library's query functions for testing the component.
13 | */
14 | export const renderWithProviders = (ui, extendedRenderOptions = {}) => {
15 | const {
16 | preloadedState = {},
17 | // Automatically create a store instance if no store was passed in
18 | store = makeStore(preloadedState),
19 | userEventOptions,
20 | ...renderOptions
21 | } = extendedRenderOptions
22 |
23 | const Wrapper = ({ children }) => (
24 | {children}
25 | )
26 |
27 | // Return an object with the store and all of RTL's query functions
28 | return {
29 | store,
30 | user: userEvent.setup(userEventOptions),
31 | ...render(ui, { wrapper: Wrapper, ...renderOptions }),
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 | .yarn/
6 | package-lock.json
7 | yarn.lock
8 |
9 | # Expo
10 | .expo/
11 | dist/
12 | web-build/
13 |
14 | # Native
15 | *.orig.*
16 | *.jks
17 | *.p8
18 | *.p12
19 | *.key
20 | *.mobileprovision
21 |
22 | # Metro
23 | .metro-health-check*
24 |
25 | # debug
26 | npm-debug.*
27 | yarn-debug.*
28 | yarn-error.*
29 |
30 | # macOS
31 | .DS_Store
32 | *.pem
33 |
34 | # local env files
35 | .env*.local
36 |
37 | # typescript
38 | *.tsbuildinfo
39 |
40 | # IDE
41 | .vscode
42 |
43 | .yalc/
44 | yalc.lock
45 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/App.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { Provider } from "react-redux"
3 | import { store } from "./src/app/store"
4 | import { Main } from "./src/Main"
5 |
6 | export const App = (): JSX.Element => (
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/README.md:
--------------------------------------------------------------------------------
1 | # Expo Template Redux TypeScript
2 |
3 | The official Redux+TS template for Expo.
4 |
5 | ## :arrow_forward: Usage
6 |
7 | ```sh
8 | npx create-expo my-app --template expo-template-redux-typescript
9 | ```
10 |
11 | # Getting Started
12 |
13 | > **Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
14 |
15 | ## Step 1: Start the Metro Server
16 |
17 | First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
18 |
19 | To start Metro, run the following command from the _root_ of your React Native project:
20 |
21 | ```bash
22 | # using npm
23 | npm start
24 | ```
25 |
26 | ```bash
27 | # OR using Yarn
28 | yarn start
29 | ```
30 |
31 | ## Step 2: Start your Application
32 |
33 | Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
34 |
35 | ### For Android
36 |
37 | ```bash
38 | # using npm
39 | npm run android
40 | ```
41 |
42 | ```bash
43 | # OR using Yarn
44 | yarn android
45 | ```
46 |
47 | ### For iOS
48 |
49 | ```bash
50 | # using npm
51 | npm run ios
52 | ```
53 |
54 | ```bash
55 | # OR using Yarn
56 | yarn ios
57 | ```
58 |
59 | If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
60 |
61 | This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
62 |
63 | ## Step 3: Modifying your App
64 |
65 | Now that you have successfully run the app, let's modify it.
66 |
67 | 1. Open `App.tsx` in your text editor of choice and edit some lines.
68 | 2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes!
69 |
70 | For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes!
71 |
72 | ## Congratulations! :tada:
73 |
74 | You've successfully run and modified your Expo App. :partying_face:
75 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-template-redux-typescript",
4 | "slug": "expo-template-redux-typescript",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "newArchEnabled": true,
10 | "splash": {
11 | "image": "./assets/splash-icon.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "ios": {
16 | "supportsTablet": true
17 | },
18 | "android": {
19 | "adaptiveIcon": {
20 | "foregroundImage": "./assets/adaptive-icon.png",
21 | "backgroundColor": "#ffffff"
22 | }
23 | },
24 | "web": {
25 | "favicon": "./assets/favicon.png"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/expo-template-redux-typescript/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/expo-template-redux-typescript/assets/favicon.png
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/expo-template-redux-typescript/assets/icon.png
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/assets/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/expo-template-redux-typescript/assets/splash-icon.png
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/babel.config.js:
--------------------------------------------------------------------------------
1 | /** @import { ConfigFunction } from "@babel/core" */
2 | /** @import { BabelPresetExpoOptions } from "babel-preset-expo" */
3 |
4 | /**
5 | * @satisfies {ConfigFunction}
6 | */
7 | const config = api => {
8 | api.cache.forever()
9 |
10 | return {
11 | presets: [
12 | /**
13 | * @satisfies {['babel-preset-expo', BabelPresetExpoOptions?]}
14 | */
15 | (["babel-preset-expo"]),
16 | ],
17 | }
18 | }
19 |
20 | module.exports = config
21 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import prettierConfig from "eslint-config-prettier/flat"
3 | import jestPlugin from "eslint-plugin-jest"
4 | import reactPlugin from "eslint-plugin-react"
5 | import reactHooksPlugin from "eslint-plugin-react-hooks"
6 | import globals from "globals"
7 | import { config, configs } from "typescript-eslint"
8 |
9 | const eslintConfig = config(
10 | {
11 | name: "global-ignores",
12 | ignores: [
13 | "**/*.snap",
14 | "**/dist/",
15 | "**/.yalc/",
16 | "**/build/",
17 | "**/temp/",
18 | "**/.temp/",
19 | "**/.tmp/",
20 | "**/.yarn/",
21 | "**/coverage/",
22 | ],
23 | },
24 | {
25 | name: `${js.meta.name}/recommended`,
26 | ...js.configs.recommended,
27 | },
28 | configs.strictTypeChecked,
29 | configs.stylisticTypeChecked,
30 | {
31 | name: `${jestPlugin.meta.name}/recommended`,
32 | ...jestPlugin.configs["flat/recommended"],
33 | },
34 | {
35 | name: "eslint-plugin-react/jsx-runtime",
36 | ...reactPlugin.configs.flat["jsx-runtime"],
37 | },
38 | reactHooksPlugin.configs["recommended-latest"],
39 | {
40 | name: "main",
41 | linterOptions: {
42 | reportUnusedDisableDirectives: 2,
43 | },
44 | languageOptions: {
45 | ecmaVersion: 2020,
46 | globals: globals.node,
47 | parserOptions: {
48 | projectService: true,
49 | tsconfigRootDir: import.meta.dirname,
50 | },
51 | },
52 | rules: {
53 | "no-undef": [0],
54 | "no-restricted-imports": [
55 | 2,
56 | {
57 | paths: [
58 | {
59 | name: "react-redux",
60 | importNames: ["useSelector", "useStore", "useDispatch"],
61 | message:
62 | "Please use pre-typed versions from `src/app/hooks.ts` instead.",
63 | },
64 | ],
65 | },
66 | ],
67 | "@typescript-eslint/consistent-type-definitions": [2, "type"],
68 | "@typescript-eslint/consistent-type-imports": [
69 | 2,
70 | {
71 | prefer: "type-imports",
72 | fixStyle: "separate-type-imports",
73 | disallowTypeAnnotations: true,
74 | },
75 | ],
76 | },
77 | },
78 | {
79 | name: "commonjs",
80 | files: ["metro.config.js"],
81 | languageOptions: {
82 | sourceType: "commonjs",
83 | },
84 | rules: {
85 | "@typescript-eslint/no-require-imports": [
86 | 0,
87 | [{ allow: [], allowAsImport: false }],
88 | ],
89 | },
90 | },
91 |
92 | prettierConfig,
93 | )
94 |
95 | export default eslintConfig
96 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 | expo-env.d.ts
11 | build/
12 |
13 | # Native
14 | *.orig.*
15 | *.jks
16 | *.p8
17 | *.p12
18 | *.key
19 | *.mobileprovision
20 |
21 | # Metro
22 | .metro-health-check*
23 |
24 | # debug
25 | npm-debug.*
26 | yarn-debug.*
27 | yarn-error.*
28 |
29 | # macOS
30 | .DS_Store
31 | *.pem
32 |
33 | # local env files
34 | .env*.local
35 |
36 | # typescript
37 | *.tsbuildinfo
38 |
39 | # testing
40 | /coverage
41 |
42 | # Yarn
43 | .yarn/*
44 | !.yarn/patches
45 | !.yarn/plugins
46 | !.yarn/releases
47 | !.yarn/sdks
48 | !.yarn/versions
49 |
50 | # IDE
51 | .vscode
52 |
53 | .yalc/
54 | yalc.lock
55 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.gif" {
2 | const logo: number
3 | export default logo
4 | }
5 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/index.ts:
--------------------------------------------------------------------------------
1 | import { registerRootComponent } from "expo"
2 | import { App } from "./App"
3 |
4 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
5 | // It also ensures that whether you load the app in Expo Go or in a native build,
6 | // the environment is set up appropriately
7 | registerRootComponent(App)
8 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/jest-setup.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/react-native"
2 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "jest"
2 |
3 | const config: Config = {
4 | preset: "jest-expo",
5 | verbose: true,
6 | /**
7 | * Without this we will get the following error:
8 | * `SyntaxError: Cannot use import statement outside a module`
9 | */
10 | transformIgnorePatterns: [
11 | "node_modules/(?!((jest-)?react-native|...|react-redux))",
12 | ],
13 | /**
14 | * React Native's `jest` preset includes a
15 | * [polyfill for `window`](https://github.com/facebook/react-native/blob/acb634bc9662c1103bc7c8ca83cfdc62516d0060/packages/react-native/jest/setup.js#L61-L66).
16 | * This polyfill causes React-Redux to use `useEffect`
17 | * instead of `useLayoutEffect` for the `useIsomorphicLayoutEffect` hook.
18 | * As a result, nested component updates may not be properly batched
19 | * when using the `connect` API, leading to potential issues.
20 | */
21 | globals: {
22 | window: undefined,
23 | navigator: {
24 | product: "ReactNative",
25 | },
26 | },
27 | setupFilesAfterEnv: ["/jest-setup.ts"],
28 | fakeTimers: {
29 | enableGlobally: true,
30 | advanceTimers: true,
31 | },
32 | }
33 |
34 | export default config
35 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/metro.base.config.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultConfig } from "expo/metro-config"
2 | import type { MetroConfig } from "metro-config"
3 | import { mergeConfig } from "metro-config"
4 |
5 | const config: MetroConfig = mergeConfig(getDefaultConfig(__dirname))
6 |
7 | export { config }
8 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/metro.config.js:
--------------------------------------------------------------------------------
1 | require("ts-node/register")
2 |
3 | const { config } = require("./metro.base.config.ts")
4 |
5 | module.exports = config
6 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo-template-redux-typescript",
3 | "version": "1.0.0",
4 | "description": "The official Redux+TS template for Expo",
5 | "main": "index.ts",
6 | "scripts": {
7 | "android": "expo start --android",
8 | "build": "expo prebuild -p android && react-native bundle --entry-file index.ts --bundle-output build/bundle.js --platform android --assets-dest build/assets",
9 | "format:check": "prettier --check .",
10 | "format": "prettier --write .",
11 | "ios": "expo start --ios",
12 | "lint:fix": "eslint --fix .",
13 | "lint": "eslint .",
14 | "start": "expo start",
15 | "test": "jest",
16 | "type-check": "tsc --noEmit",
17 | "web": "expo start --web"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/reduxjs/redux-templates.git",
22 | "directory": "packages/expo-template-redux-typescript"
23 | },
24 | "dependencies": {
25 | "@reduxjs/toolkit": "^2.6.1",
26 | "expo": "~52.0.42",
27 | "expo-status-bar": "~2.0.1",
28 | "react": "19.0.0",
29 | "react-native": "0.78.2",
30 | "react-redux": "^9.2.0"
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.26.10",
34 | "@eslint/js": "^9.23.0",
35 | "@react-native-community/cli": "^18.0.0",
36 | "@react-native-community/cli-platform-android": "^17.0.0",
37 | "@react-native-community/cli-platform-ios": "^17.0.0",
38 | "@testing-library/react-native": "^13.2.0",
39 | "@types/babel__core": "^7.20.5",
40 | "@types/jest": "^29.5.14",
41 | "@types/node": "^22.14.0",
42 | "@types/react": "^19.1.0",
43 | "@types/react-test-renderer": "^19.1.0",
44 | "eslint": "^9.23.0",
45 | "eslint-config-prettier": "^10.1.1",
46 | "eslint-plugin-jest": "^28.11.0",
47 | "eslint-plugin-react": "^7.37.4",
48 | "eslint-plugin-react-hooks": "^5.2.0",
49 | "globals": "^16.0.0",
50 | "jest": "^29.7.0",
51 | "jest-expo": "^52.0.6",
52 | "prettier": "^3.5.3",
53 | "react-test-renderer": "19.0.0",
54 | "ts-node": "^10.9.2",
55 | "typescript": "^5.8.2",
56 | "typescript-eslint": "^8.29.0"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/app/createAppSlice.ts:
--------------------------------------------------------------------------------
1 | import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2 |
3 | // `buildCreateSlice` allows us to create a slice with async thunks.
4 | export const createAppSlice = buildCreateSlice({
5 | creators: { asyncThunk: asyncThunkCreator },
6 | })
7 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | // These imports are restricted elsewhere to ensure consistent
3 | // usage of typed hooks throughout the application.
4 | // We disable the ESLint rule here because this is the designated place
5 | // for importing and re-exporting the typed versions of hooks.
6 | /* eslint-disable no-restricted-imports */
7 | import { useEffect } from "react"
8 | import { Animated, useAnimatedValue, useWindowDimensions } from "react-native"
9 | import { useDispatch, useSelector } from "react-redux"
10 | import type { AppDispatch, RootState } from "./store"
11 |
12 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
13 | export const useAppDispatch = useDispatch.withTypes()
14 | export const useAppSelector = useSelector.withTypes()
15 |
16 | /**
17 | * Custom React hook for calculating viewport units
18 | * based on the current window dimensions.
19 | *
20 | * @returns An object containing the calculated viewport heigh and width values.
21 | */
22 | export const useViewportUnits = (): { vh: number; vw: number } => {
23 | const { width, height } = useWindowDimensions()
24 |
25 | const vh = height / 100
26 | const vw = width / 100
27 |
28 | return { vh, vw }
29 | }
30 |
31 | /**
32 | * Custom React hook for creating a bounce animation effect.
33 | *
34 | * @param value - The maximum height to which the object should bounce. Defaults to 10 if not provided.
35 | * @returns The {@linkcode Animated.Value} object that can be used to drive animations.
36 | */
37 | export const useBounceAnimation = (value = 10): Animated.Value => {
38 | const bounce = useAnimatedValue(0)
39 |
40 | bounce.interpolate({
41 | inputRange: [-300, -100, 0, 100, 101],
42 | outputRange: [300, 0, 1, 0, 0],
43 | })
44 |
45 | useEffect(() => {
46 | Animated.loop(
47 | Animated.sequence([
48 | Animated.timing(bounce, {
49 | toValue: value,
50 | duration: 1500,
51 | useNativeDriver: true,
52 | }),
53 | Animated.timing(bounce, {
54 | toValue: 0,
55 | duration: 1500,
56 | useNativeDriver: true,
57 | }),
58 | ]),
59 | ).start()
60 | }, [bounce, value])
61 |
62 | return bounce
63 | }
64 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import type { Action, ThunkAction } from "@reduxjs/toolkit"
2 | import { combineSlices, configureStore } from "@reduxjs/toolkit"
3 | import { setupListeners } from "@reduxjs/toolkit/query"
4 | import { counterSlice } from "../features/counter/counterSlice"
5 | import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
6 |
7 | // `combineSlices` automatically combines the reducers using
8 | // their `reducerPath`s, therefore we no longer need to call `combineReducers`.
9 | const rootReducer = combineSlices(counterSlice, quotesApiSlice)
10 | // Infer the `RootState` type from the root reducer
11 | export type RootState = ReturnType
12 |
13 | // The store setup is wrapped in `makeStore` to allow reuse
14 | // when setting up tests that need the same store config
15 | export const makeStore = (preloadedState?: Partial) => {
16 | const store = configureStore({
17 | reducer: rootReducer,
18 | // Adding the api middleware enables caching, invalidation, polling,
19 | // and other useful features of `rtk-query`.
20 | middleware: getDefaultMiddleware => {
21 | return getDefaultMiddleware().concat(quotesApiSlice.middleware)
22 | },
23 | preloadedState,
24 | })
25 | // configure listeners using the provided defaults
26 | // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
27 | setupListeners(store.dispatch)
28 | return store
29 | }
30 |
31 | export const store = makeStore()
32 |
33 | // Infer the type of `store`
34 | export type AppStore = typeof store
35 | // Infer the `AppDispatch` type from the store itself
36 | export type AppDispatch = AppStore["dispatch"]
37 | export type AppThunk = ThunkAction<
38 | ThunkReturnType,
39 | RootState,
40 | unknown,
41 | Action
42 | >
43 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/AsyncButton.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX, PropsWithChildren } from "react"
2 | import type {
3 | GestureResponderEvent,
4 | PressableProps,
5 | ViewStyle,
6 | } from "react-native"
7 | import {
8 | Animated,
9 | Pressable,
10 | StyleSheet,
11 | View,
12 | useAnimatedValue,
13 | } from "react-native"
14 |
15 | type AsyncButtonProps = PressableProps & PropsWithChildren
16 |
17 | export const AsyncButton = ({
18 | onPress,
19 | style,
20 | children,
21 | ...restProps
22 | }: AsyncButtonProps): JSX.Element => {
23 | const progress = useAnimatedValue(0)
24 | const opacity = useAnimatedValue(1)
25 |
26 | const _onPress = (e: GestureResponderEvent) => {
27 | progress.setValue(0)
28 | opacity.setValue(1)
29 |
30 | onPress?.(e)
31 |
32 | Animated.timing(progress, {
33 | toValue: 1,
34 | duration: 1000,
35 | useNativeDriver: false,
36 | }).start(({ finished }) => {
37 | if (!finished) {
38 | return
39 | }
40 |
41 | Animated.timing(opacity, {
42 | toValue: 0,
43 | duration: 200,
44 | useNativeDriver: false,
45 | }).start()
46 | })
47 | }
48 |
49 | const progressInterpolate = progress.interpolate({
50 | inputRange: [0, 1],
51 | outputRange: ["0%", "100%"],
52 | extrapolate: "clamp",
53 | })
54 |
55 | const progressStyle: Animated.WithAnimatedObject = {
56 | width: progressInterpolate,
57 | opacity,
58 | }
59 |
60 | return (
61 |
62 |
63 |
64 |
65 | {children}
66 |
67 | )
68 | }
69 |
70 | const styles = StyleSheet.create({
71 | progress: {
72 | position: "absolute",
73 | top: 0,
74 | bottom: 0,
75 | left: 0,
76 | backgroundColor: "rgba(112,76,182, 0.15)",
77 | },
78 | })
79 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { useCallback } from "react"
3 | import type { TouchableOpacityProps } from "react-native"
4 | import { Alert, Linking, TouchableOpacity } from "react-native"
5 |
6 | type ExternalLinkProps = TouchableOpacityProps & {
7 | url: string
8 | }
9 |
10 | export const ExternalLink = ({
11 | url,
12 | ...touchableOpacityProps
13 | }: ExternalLinkProps): JSX.Element => {
14 | const onPress = useCallback(async () => {
15 | const supported = await Linking.canOpenURL(url)
16 |
17 | if (supported) {
18 | await Linking.openURL(url)
19 | } else {
20 | Alert.alert(`Don't know how to open this URL: ${url}`)
21 | }
22 | }, [url])
23 |
24 | return (
25 | {
29 | void onPress()
30 | }}
31 | />
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/ExternalLinks.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { Fragment } from "react"
3 | import { StyleSheet, Text, useColorScheme, View } from "react-native"
4 | import { Colors } from "../constants/Colors"
5 | import { ExternalLink } from "./ExternalLink"
6 |
7 | export type Link = {
8 | id: number
9 | title: string
10 | url: string
11 | description: string
12 | }
13 |
14 | type ExternalLinksProps = {
15 | links: Link[]
16 | }
17 |
18 | export const ExternalLinks = ({ links }: ExternalLinksProps): JSX.Element => {
19 | const isDarkMode = useColorScheme() === "dark"
20 |
21 | return (
22 |
23 | {links.map(({ description, id, title, url }) => (
24 |
25 |
31 |
32 | {title}
33 |
39 | {description}
40 |
41 |
42 |
43 | ))}
44 |
45 | )
46 | }
47 |
48 | const styles = StyleSheet.create({
49 | container: {
50 | marginTop: 32,
51 | paddingHorizontal: 24,
52 | },
53 | linkContainer: {
54 | flexWrap: "wrap",
55 | flexDirection: "row",
56 | justifyContent: "space-between",
57 | alignItems: "center",
58 | paddingVertical: 8,
59 | },
60 | link: {
61 | flex: 2,
62 | fontSize: 18,
63 | fontWeight: "400",
64 | color: Colors.primary,
65 | },
66 | description: {
67 | flex: 3,
68 | paddingVertical: 16,
69 | fontWeight: "400",
70 | fontSize: 18,
71 | },
72 | separator: {
73 | height: StyleSheet.hairlineWidth,
74 | },
75 | })
76 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { Animated, StyleSheet, View, useColorScheme } from "react-native"
3 | import { useBounceAnimation, useViewportUnits } from "../app/hooks"
4 | import { Colors } from "../constants/Colors"
5 | import logo from "./logo.gif"
6 |
7 | export const Header = (): JSX.Element => {
8 | const isDarkMode = useColorScheme() === "dark"
9 | const { vh } = useViewportUnits()
10 | const bounce = useBounceAnimation()
11 | const height = 40 * vh
12 |
13 | return (
14 |
20 |
25 |
26 | )
27 | }
28 |
29 | const styles = StyleSheet.create({
30 | container: {
31 | flexDirection: "row",
32 | justifyContent: "center",
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/Section.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX, PropsWithChildren } from "react"
2 | import { StyleSheet, Text, View, useColorScheme } from "react-native"
3 | import { Colors } from "../constants/Colors"
4 |
5 | type SectionProps = PropsWithChildren<{
6 | title: string
7 | }>
8 |
9 | export const Section = ({ children, title }: SectionProps): JSX.Element => {
10 | const isDarkMode = useColorScheme() === "dark"
11 |
12 | return (
13 |
14 |
20 | {title}
21 |
22 |
28 | {children}
29 |
30 |
31 | )
32 | }
33 |
34 | const styles = StyleSheet.create({
35 | sectionContainer: {
36 | marginTop: 32,
37 | paddingHorizontal: 24,
38 | },
39 | sectionTitle: {
40 | fontSize: 24,
41 | fontWeight: "600",
42 | },
43 | sectionDescription: {
44 | marginTop: 8,
45 | fontSize: 18,
46 | fontWeight: "400",
47 | },
48 | })
49 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/components/logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/expo-template-redux-typescript/src/components/logo.gif
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/constants/Colors.ts:
--------------------------------------------------------------------------------
1 | type AllColors = {
2 | primary: string
3 | white: string
4 | lighter: string
5 | light: string
6 | dark: string
7 | darker: string
8 | black: string
9 | }
10 |
11 | export const Colors: AllColors = {
12 | light: "#DAE1E7",
13 | lighter: "#F3F3F3",
14 | white: "#FFF",
15 | dark: "#444",
16 | darker: "#222",
17 | black: "#000",
18 | primary: "#1292B4",
19 | }
20 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1): Promise<{ data: number }> =>
3 | new Promise<{ data: number }>(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/features/counter/counterSlice.test.ts:
--------------------------------------------------------------------------------
1 | import { makeStore } from "../../app/store"
2 | import type { CounterSliceState } from "./counterSlice"
3 | import {
4 | counterSlice,
5 | decrement,
6 | increment,
7 | incrementByAmount,
8 | selectCount,
9 | } from "./counterSlice"
10 |
11 | describe("counter reducer", () => {
12 | const initialState: CounterSliceState = {
13 | value: 3,
14 | status: "idle",
15 | }
16 |
17 | let store = makeStore()
18 |
19 | beforeEach(() => {
20 | store = makeStore({ counter: initialState })
21 | })
22 |
23 | it("should handle initial state", () => {
24 | expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
25 | value: 0,
26 | status: "idle",
27 | })
28 | })
29 |
30 | it("should handle increment", () => {
31 | expect(selectCount(store.getState())).toBe(3)
32 |
33 | store.dispatch(increment())
34 |
35 | expect(selectCount(store.getState())).toBe(4)
36 | })
37 |
38 | it("should handle decrement", () => {
39 | expect(selectCount(store.getState())).toBe(3)
40 |
41 | store.dispatch(decrement())
42 |
43 | expect(selectCount(store.getState())).toBe(2)
44 | })
45 |
46 | it("should handle incrementByAmount", () => {
47 | expect(selectCount(store.getState())).toBe(3)
48 |
49 | store.dispatch(incrementByAmount(2))
50 |
51 | expect(selectCount(store.getState())).toBe(5)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/features/quotes/quotesApiSlice.ts:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import `createApi`
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
3 |
4 | type Quote = {
5 | id: number
6 | quote: string
7 | author: string
8 | }
9 |
10 | type QuotesApiResponse = {
11 | quotes: Quote[]
12 | total: number
13 | skip: number
14 | limit: number
15 | }
16 |
17 | // Define a service using a base URL and expected endpoints
18 | export const quotesApiSlice = createApi({
19 | baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
20 | reducerPath: "quotesApi",
21 | // Tag types are used for caching and invalidation.
22 | tagTypes: ["Quotes"],
23 | endpoints: build => ({
24 | // Supply generics for the return type (in this case `QuotesApiResponse`)
25 | // and the expected query argument. If there is no argument, use `void`
26 | // for the argument type instead.
27 | getQuotes: build.query({
28 | query: (limit = 10) => `?limit=${limit.toString()}`,
29 | // `providesTags` determines which 'tag' is attached to the
30 | // cached data returned by the query.
31 | providesTags: (_result, _error, id) => [{ type: "Quotes", id }],
32 | }),
33 | }),
34 | })
35 |
36 | // Hooks are auto-generated by RTK-Query
37 | // Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
38 | export const { useGetQuotesQuery } = quotesApiSlice
39 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/src/utils/test-utils.tsx:
--------------------------------------------------------------------------------
1 | import type { RenderOptions } from "@testing-library/react-native"
2 | import { render, userEvent } from "@testing-library/react-native"
3 | import type { PropsWithChildren, ReactElement } from "react"
4 | import { Provider } from "react-redux"
5 | import type { AppStore, RootState } from "../app/store"
6 | import { makeStore } from "../app/store"
7 |
8 | /**
9 | * This type extends the default options for
10 | * React Testing Library's render function. It allows for
11 | * additional configuration such as specifying an initial Redux state and
12 | * a custom store instance.
13 | */
14 | type ExtendedRenderOptions = Omit & {
15 | /**
16 | * Defines a specific portion or the entire initial state for the Redux store.
17 | * This is particularly useful for initializing the state in a
18 | * controlled manner during testing, allowing components to be rendered
19 | * with predetermined state conditions.
20 | */
21 | preloadedState?: Partial
22 |
23 | /**
24 | * Allows the use of a specific Redux store instance instead of a
25 | * default or global store. This flexibility is beneficial when
26 | * testing components with unique store requirements or when isolating
27 | * tests from a global store state. The custom store should be configured
28 | * to match the structure and middleware of the store used by the application.
29 | *
30 | * @default makeStore(preloadedState)
31 | */
32 | store?: AppStore
33 | }
34 |
35 | /**
36 | * Renders the given React element with Redux Provider and custom store.
37 | * This function is useful for testing components that are connected to the Redux store.
38 | *
39 | * @param ui - The React component or element to render.
40 | * @param extendedRenderOptions - Optional configuration options for rendering. This includes `preloadedState` for initial Redux state and `store` for a specific Redux store instance. Any additional properties are passed to React Testing Library's render function.
41 | * @returns An object containing the Redux store used in the render, User event API for simulating user interactions in tests, and all of React Testing Library's query functions for testing the component.
42 | */
43 | export const renderWithProviders = (
44 | ui: ReactElement,
45 | extendedRenderOptions: ExtendedRenderOptions = {},
46 | ) => {
47 | const {
48 | preloadedState = {},
49 | // Automatically create a store instance if no store was passed in
50 | store = makeStore(preloadedState),
51 | ...renderOptions
52 | } = extendedRenderOptions
53 |
54 | const Wrapper = ({ children }: PropsWithChildren) => (
55 | {children}
56 | )
57 |
58 | // Return an object with the store and all of RTL's query functions
59 | return {
60 | store,
61 | user: userEvent.setup(),
62 | ...render(ui, { wrapper: Wrapper, ...renderOptions }),
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/expo-template-redux-typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | ios/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | *.hprof
33 | .cxx/
34 | *.keystore
35 | !debug.keystore
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 | .yarn/
43 | template/package-lock.json
44 | template/yarn.lock
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/
52 |
53 | **/fastlane/report.xml
54 | **/fastlane/Preview.html
55 | **/fastlane/screenshots
56 | **/fastlane/test_output
57 |
58 | # Bundle artifact
59 | *.jsbundle
60 |
61 | # Ruby / CocoaPods
62 | /ios/Pods/
63 | /vendor/bundle/
64 |
65 | # Temporary files created by Metro to check the health of the file watcher
66 | .metro-health-check*
67 |
68 | # testing
69 | /coverage
70 |
71 | #IDE
72 | .vscode
73 |
74 | .yalc/
75 | yalc.lock
76 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-template-redux-typescript",
3 | "version": "0.0.1",
4 | "description": "The official Redux+TS template for React Native",
5 | "files": [
6 | "template",
7 | "template.config.js"
8 | ],
9 | "homepage": "https://github.com/reduxjs/redux-templates/tree/HEAD/packages/react-native-template-redux-typescript#readme",
10 | "bugs": {
11 | "url": "https://github.com/reduxjs/redux-templates/issues"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/reduxjs/redux-templates.git",
16 | "directory": "packages/react-native-template-redux-typescript"
17 | },
18 | "scripts": {
19 | "format": "prettier --write \"./**/*.{js,ts,jsx,tsx,mjs,cts,md}\"",
20 | "lint": "eslint './template/src/**/*.{ts,tsx,js,jsx}'",
21 | "test": "jest",
22 | "type-check": "tsc"
23 | },
24 | "keywords": [
25 | "react-native",
26 | "typescript",
27 | "jest",
28 | "template",
29 | "boilerplate"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Configuration settings for a React-Native template.
3 | *
4 | * @typedef {Object} TemplateConfig
5 | * @property {string} placeholderName A unique identifier or name for the placeholder within the template.
6 | * @property {string} templateDir The file path to the directory where the template is located.
7 | * @property {string} [postInitScript] The path to a script that should be executed after the template initialization is complete (optional).
8 | * @property {string} [titlePlaceholder] A placeholder for the title within the template (optional).
9 | */
10 |
11 | /**
12 | * @type TemplateConfig
13 | */
14 | const templateConfig = {
15 | placeholderName: "ReactNativeReduxTemplate",
16 | templateDir: "./template",
17 | titlePlaceholder: "react-native-template",
18 | }
19 |
20 | module.exports = templateConfig
21 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | **/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | dist/
29 | .idea
30 | .gradle
31 | local.properties
32 | *.iml
33 | *.hprof
34 | .cxx/
35 | *.keystore
36 | !debug.keystore
37 | .kotlin/
38 |
39 | # node.js
40 | #
41 | node_modules/
42 | npm-debug.log
43 | yarn-error.log
44 |
45 | # fastlane
46 | #
47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48 | # screenshots whenever they are needed.
49 | # For more information about the recommended setup visit:
50 | # https://docs.fastlane.tools/best-practices/source-control/
51 |
52 | **/fastlane/report.xml
53 | **/fastlane/Preview.html
54 | **/fastlane/screenshots
55 | **/fastlane/test_output
56 |
57 | # Bundle artifact
58 | *.jsbundle
59 |
60 | # Ruby / CocoaPods
61 | **/Pods/
62 | /vendor/bundle/
63 |
64 | # Temporary files created by Metro to check the health of the file watcher
65 | .metro-health-check*
66 |
67 | # testing
68 | /coverage
69 |
70 | # Yarn
71 | .yarn/*
72 | !.yarn/patches
73 | !.yarn/plugins
74 | !.yarn/releases
75 | !.yarn/sdks
76 | !.yarn/versions
77 |
78 | #IDE
79 | .vscode
80 |
81 | .yalc/
82 | yalc.lock
83 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/App.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import {
3 | SafeAreaView,
4 | ScrollView,
5 | StatusBar,
6 | StyleSheet,
7 | Text,
8 | View,
9 | useColorScheme,
10 | } from "react-native"
11 | import {
12 | DebugInstructions,
13 | HermesBadge,
14 | LearnMoreLinks,
15 | ReloadInstructions,
16 | } from "react-native/Libraries/NewAppScreen"
17 | import { Header } from "./src/components/Header"
18 | import { LearnReduxLinks } from "./src/components/LearnReduxLinks"
19 | import { Section } from "./src/components/Section"
20 | import { TypedColors } from "./src/constants/TypedColors"
21 | import { Counter } from "./src/features/counter/Counter"
22 | import { Quotes } from "./src/features/quotes/Quotes"
23 |
24 | export const App = (): JSX.Element => {
25 | const isDarkMode = useColorScheme() === "dark"
26 |
27 | const backgroundStyle = {
28 | backgroundColor: isDarkMode ? TypedColors.darker : TypedColors.lighter,
29 | }
30 |
31 | return (
32 |
33 |
37 |
41 |
42 |
43 |
48 |
49 |
50 |
51 | Edit App.tsx to change this
52 | screen and then come back to see your edits.
53 |
54 |
57 |
60 |
61 | Discover what to do next with Redux:
62 |
63 |
64 |
65 | Read the docs to discover what to do next:
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
74 | const styles = StyleSheet.create({
75 | highlight: {
76 | fontWeight: "700",
77 | },
78 | })
79 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures.
7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
9 | gem 'xcodeproj', '< 1.26.0'
10 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/debug.keystore
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/java/com/ReactNativeReduxTemplate/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ReactNativeReduxTemplate
2 |
3 | import com.facebook.react.ReactActivity
4 | import com.facebook.react.ReactActivityDelegate
5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
6 | import com.facebook.react.defaults.DefaultReactActivityDelegate
7 |
8 | class MainActivity : ReactActivity() {
9 |
10 | /**
11 | * Returns the name of the main component registered from JavaScript. This is used to schedule
12 | * rendering of the component.
13 | */
14 | override fun getMainComponentName(): String = "ReactNativeReduxTemplate"
15 |
16 | /**
17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
19 | */
20 | override fun createReactActivityDelegate(): ReactActivityDelegate =
21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
22 | }
23 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/java/com/ReactNativeReduxTemplate/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.ReactNativeReduxTemplate
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeHost
8 | import com.facebook.react.ReactPackage
9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11 | import com.facebook.react.defaults.DefaultReactNativeHost
12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping
13 | import com.facebook.soloader.SoLoader
14 |
15 | class MainApplication : Application(), ReactApplication {
16 |
17 | override val reactNativeHost: ReactNativeHost =
18 | object : DefaultReactNativeHost(this) {
19 | override fun getPackages(): List =
20 | PackageList(this).packages.apply {
21 | // Packages that cannot be autolinked yet can be added manually here, for example:
22 | // add(MyReactNativePackage())
23 | }
24 |
25 | override fun getJSMainModuleName(): String = "index"
26 |
27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
28 |
29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
31 | }
32 |
33 | override val reactHost: ReactHost
34 | get() = getDefaultReactHost(applicationContext, reactNativeHost)
35 |
36 | override fun onCreate() {
37 | super.onCreate()
38 | SoLoader.init(this, OpenSourceMergedSoMapping)
39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
40 | // If you opted-in for the New Architecture, we load the native entry point for this app.
41 | load()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ReactNativeReduxTemplate
3 |
4 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "35.0.0"
4 | minSdkVersion = 24
5 | compileSdkVersion = 35
6 | targetSdkVersion = 34
7 | ndkVersion = "26.1.10909125"
8 | kotlinVersion = "1.9.24"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 |
25 | # Use this property to specify which architecture you want to build.
26 | # You can also override it from the CLI using
27 | # ./gradlew -PreactNativeArchitectures=x86_64
28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
29 |
30 | # Use this property to enable support to the new architecture.
31 | # This will allow you to use TurboModules and the Fabric render in
32 | # your application. You should enable this flag either if you want
33 | # to write custom TurboModules/Fabric components OR use libraries that
34 | # are providing them.
35 | newArchEnabled=true
36 |
37 | # Use this property to enable or disable the Hermes JS engine.
38 | # If set to false, you will be using JSC instead.
39 | hermesEnabled=true
40 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
2 | plugins { id("com.facebook.react.settings") }
3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
4 | rootProject.name = 'ReactNativeReduxTemplate'
5 | include ':app'
6 | includeBuild('../node_modules/@react-native/gradle-plugin')
7 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeReduxTemplate",
3 | "displayName": "ReactNativeReduxTemplate"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/babel.config.js:
--------------------------------------------------------------------------------
1 | /** @import { ConfigFunction } from '@babel/core' */
2 |
3 | /**
4 | * @satisfies {ConfigFunction}
5 | */
6 | const config = api => {
7 | api.cache.using(() => process.env.NODE_ENV)
8 |
9 | return {
10 | presets: [["module:@react-native/babel-preset"]],
11 | }
12 | }
13 |
14 | module.exports = config
15 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import prettierConfig from "eslint-config-prettier/flat"
3 | import jestPlugin from "eslint-plugin-jest"
4 | import reactPlugin from "eslint-plugin-react"
5 | import reactHooksPlugin from "eslint-plugin-react-hooks"
6 | import globals from "globals"
7 | import { config, configs } from "typescript-eslint"
8 |
9 | const eslintConfig = config(
10 | {
11 | name: "global-ignores",
12 | ignores: [
13 | "**/*.snap",
14 | "**/dist/",
15 | "**/.yalc/",
16 | "**/build/",
17 | "**/temp/",
18 | "**/.temp/",
19 | "**/.tmp/",
20 | "**/.yarn/",
21 | "**/coverage/",
22 | ],
23 | },
24 | {
25 | name: `${js.meta.name}/recommended`,
26 | ...js.configs.recommended,
27 | },
28 | configs.strictTypeChecked,
29 | configs.stylisticTypeChecked,
30 | {
31 | name: `${jestPlugin.meta.name}/recommended`,
32 | ...jestPlugin.configs["flat/recommended"],
33 | },
34 | {
35 | name: "eslint-plugin-react/jsx-runtime",
36 | ...reactPlugin.configs.flat["jsx-runtime"],
37 | },
38 | reactHooksPlugin.configs["recommended-latest"],
39 | {
40 | name: "main",
41 | linterOptions: {
42 | reportUnusedDisableDirectives: 2,
43 | },
44 | languageOptions: {
45 | ecmaVersion: 2020,
46 | globals: globals.node,
47 | parserOptions: {
48 | projectService: true,
49 | tsconfigRootDir: import.meta.dirname,
50 | },
51 | },
52 | rules: {
53 | "no-undef": [0],
54 | "no-restricted-imports": [
55 | 2,
56 | {
57 | paths: [
58 | {
59 | name: "react-redux",
60 | importNames: ["useSelector", "useStore", "useDispatch"],
61 | message:
62 | "Please use pre-typed versions from `src/app/hooks.ts` instead.",
63 | },
64 | ],
65 | },
66 | ],
67 | "@typescript-eslint/consistent-type-definitions": [2, "type"],
68 | "@typescript-eslint/consistent-type-imports": [
69 | 2,
70 | {
71 | prefer: "type-imports",
72 | fixStyle: "separate-type-imports",
73 | disallowTypeAnnotations: true,
74 | },
75 | ],
76 | },
77 | },
78 | {
79 | name: "commonjs",
80 | files: ["metro.config.js"],
81 | languageOptions: {
82 | sourceType: "commonjs",
83 | },
84 | rules: {
85 | "@typescript-eslint/no-require-imports": [
86 | 0,
87 | [{ allow: [], allowAsImport: false }],
88 | ],
89 | },
90 | },
91 |
92 | prettierConfig,
93 | )
94 |
95 | export default eslintConfig
96 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.gif" {
2 | const logo: number
3 | export default logo
4 | }
5 |
6 | declare module "react-native/Libraries/NewAppScreen" {
7 | import type { FC } from "react"
8 | export const HermesBadge: FC
9 | }
10 |
11 | declare module "react-native/Libraries/Core/Devtools/openURLInBrowser" {
12 | export default function openURLInBrowser(url: string): void
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from "react-native"
2 | import { Provider } from "react-redux"
3 | import { App } from "./App"
4 | import { name as appName } from "./app.json"
5 | import { store } from "./src/app/store"
6 |
7 | AppRegistry.registerComponent(appName, () => () => (
8 |
9 |
10 |
11 | ))
12 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | linkage = ENV['USE_FRAMEWORKS']
12 | if linkage != nil
13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
14 | use_frameworks! :linkage => linkage.to_sym
15 | end
16 |
17 | target 'ReactNativeReduxTemplate' do
18 | config = use_native_modules!
19 |
20 | use_react_native!(
21 | :path => config[:reactNativePath],
22 | # An absolute path to your application root.
23 | :app_path => "#{Pod::Config.instance.installation_root}/.."
24 | )
25 |
26 | target 'ReactNativeReduxTemplateTests' do
27 | inherit! :complete
28 | # Pods for testing
29 | end
30 |
31 | post_install do |installer|
32 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
33 | react_native_post_install(
34 | installer,
35 | config[:reactNativePath],
36 | :mac_catalyst_enabled => false,
37 | # :ccache_enabled => true
38 | )
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : RCTAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | self.moduleName = @"ReactNativeReduxTemplate";
10 | // You can add your custom initial props in the dictionary below.
11 | // They will be passed down to the ViewController used by React Native.
12 | self.initialProps = @{};
13 |
14 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
15 | }
16 |
17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
18 | {
19 | return [self bundleURL];
20 | }
21 |
22 | - (NSURL *)bundleURL
23 | {
24 | #if DEBUG
25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
26 | #else
27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
28 | #endif
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "iphone",
5 | "scale": "2x",
6 | "size": "20x20"
7 | },
8 | {
9 | "idiom": "iphone",
10 | "scale": "3x",
11 | "size": "20x20"
12 | },
13 | {
14 | "idiom": "iphone",
15 | "scale": "2x",
16 | "size": "29x29"
17 | },
18 | {
19 | "idiom": "iphone",
20 | "scale": "3x",
21 | "size": "29x29"
22 | },
23 | {
24 | "idiom": "iphone",
25 | "scale": "2x",
26 | "size": "40x40"
27 | },
28 | {
29 | "idiom": "iphone",
30 | "scale": "3x",
31 | "size": "40x40"
32 | },
33 | {
34 | "idiom": "iphone",
35 | "scale": "2x",
36 | "size": "60x60"
37 | },
38 | {
39 | "idiom": "iphone",
40 | "scale": "3x",
41 | "size": "60x60"
42 | },
43 | {
44 | "idiom": "ios-marketing",
45 | "scale": "1x",
46 | "size": "1024x1024"
47 | }
48 | ],
49 | "info": {
50 | "author": "xcode",
51 | "version": 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "version": 1,
4 | "author": "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ReactNativeReduxTemplate
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 |
30 | NSAllowsArbitraryLoads
31 |
32 | NSAllowsLocalNetworking
33 |
34 |
35 | NSLocationWhenInUseUsageDescription
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIRequiredDeviceCapabilities
40 |
41 | arm64
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UIViewControllerBasedStatusBarAppearance
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryFileTimestamp
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | C617.1
13 |
14 |
15 |
16 | NSPrivacyAccessedAPIType
17 | NSPrivacyAccessedAPICategoryUserDefaults
18 | NSPrivacyAccessedAPITypeReasons
19 |
20 | CA92.1
21 |
22 |
23 |
24 | NSPrivacyAccessedAPIType
25 | NSPrivacyAccessedAPICategorySystemBootTime
26 | NSPrivacyAccessedAPITypeReasons
27 |
28 | 35F9.1
29 |
30 |
31 |
32 | NSPrivacyCollectedDataTypes
33 |
34 | NSPrivacyTracking
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplate/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplateTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/ios/ReactNativeReduxTemplateTests/ReactNativeReduxTemplateTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface ReactNativeReduxTemplateTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation ReactNativeReduxTemplateTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(
38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
39 | if (level >= RCTLogLevelError) {
40 | redboxError = message;
41 | }
42 | });
43 | #endif
44 |
45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
48 |
49 | foundElement = [self findSubviewInView:vc.view
50 | matching:^BOOL(UIView *view) {
51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
52 | return YES;
53 | }
54 | return NO;
55 | }];
56 | }
57 |
58 | #ifdef DEBUG
59 | RCTSetLogFunction(RCTDefaultLogFunction);
60 | #endif
61 |
62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/jest-setup.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/react-native"
2 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "jest"
2 |
3 | const config: Config = {
4 | preset: "react-native",
5 | verbose: true,
6 | /**
7 | * Without this we will get the following error:
8 | * `SyntaxError: Cannot use import statement outside a module`
9 | */
10 | transformIgnorePatterns: [
11 | "node_modules/(?!((jest-)?react-native|...|react-redux))",
12 | ],
13 | /**
14 | * React Native's `jest` preset includes a
15 | * [polyfill for `window`](https://github.com/facebook/react-native/blob/acb634bc9662c1103bc7c8ca83cfdc62516d0060/packages/react-native/jest/setup.js#L61-L66).
16 | * This polyfill causes React-Redux to use `useEffect`
17 | * instead of `useLayoutEffect` for the `useIsomorphicLayoutEffect` hook.
18 | * As a result, nested component updates may not be properly batched
19 | * when using the `connect` API, leading to potential issues.
20 | */
21 | globals: {
22 | window: undefined,
23 | navigator: {
24 | product: "ReactNative",
25 | },
26 | },
27 | setupFilesAfterEnv: ["/jest-setup.ts"],
28 | fakeTimers: {
29 | enableGlobally: true,
30 | advanceTimers: true,
31 | },
32 | }
33 |
34 | export default config
35 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/metro.config.js:
--------------------------------------------------------------------------------
1 | const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config")
2 |
3 | /**
4 | * Metro configuration
5 | * @see {@link https://reactnative.dev/docs/metro | React Native docs}
6 | * @see {@link https://facebook.github.io/metro/docs/configuration | Metro docs}
7 | *
8 | * @type {import('@react-native/metro-config').MetroConfig}
9 | */
10 | const config = mergeConfig(getDefaultConfig(__dirname))
11 |
12 | module.exports = config
13 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-template",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "build": "react-native bundle --entry-file index.js --bundle-output build/bundle.js --platform android --assets-dest build/assets",
8 | "format:check": "prettier --check .",
9 | "format": "prettier --write .",
10 | "ios": "react-native run-ios",
11 | "lint:fix": "eslint --fix .",
12 | "lint": "eslint .",
13 | "start": "react-native start",
14 | "test": "jest",
15 | "type-check": "tsc --noEmit"
16 | },
17 | "dependencies": {
18 | "@reduxjs/toolkit": "^2.6.1",
19 | "react": "19.0.0",
20 | "react-native": "^0.78.2",
21 | "react-redux": "^9.2.0"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.26.10",
25 | "@babel/preset-env": "^7.26.9",
26 | "@babel/runtime": "^7.27.0",
27 | "@eslint/js": "^9.23.0",
28 | "@react-native-community/cli": "^18.0.0",
29 | "@react-native-community/cli-platform-android": "^17.0.0",
30 | "@react-native-community/cli-platform-ios": "^17.0.0",
31 | "@react-native/babel-preset": "^0.78.2",
32 | "@react-native/metro-config": "^0.78.2",
33 | "@react-native/typescript-config": "^0.78.2",
34 | "@testing-library/react-native": "^13.2.0",
35 | "@types/babel__core": "^7.20.5",
36 | "@types/jest": "^29.5.14",
37 | "@types/node": "^22.14.0",
38 | "@types/react": "^19.1.0",
39 | "@types/react-test-renderer": "^19.1.0",
40 | "babel-jest": "^29.7.0",
41 | "eslint": "^9.23.0",
42 | "eslint-config-prettier": "^10.1.1",
43 | "eslint-plugin-jest": "^28.11.0",
44 | "eslint-plugin-react": "^7.37.4",
45 | "eslint-plugin-react-hooks": "^5.2.0",
46 | "globals": "^16.0.0",
47 | "jest": "^29.7.0",
48 | "prettier": "^3.5.3",
49 | "react-test-renderer": "19.0.0",
50 | "ts-node": "^10.9.2",
51 | "typescript": "^5.8.2",
52 | "typescript-eslint": "^8.29.0"
53 | },
54 | "engines": {
55 | "node": ">=18"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/react-native.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('@react-native-community/cli-types').UserConfig} */
2 | const config = {
3 | project: {
4 | ios: {
5 | automaticPodsInstallation: true,
6 | },
7 | },
8 | }
9 |
10 | export default config
11 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/app/createAppSlice.ts:
--------------------------------------------------------------------------------
1 | import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2 |
3 | // `buildCreateSlice` allows us to create a slice with async thunks.
4 | export const createAppSlice = buildCreateSlice({
5 | creators: { asyncThunk: asyncThunkCreator },
6 | })
7 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | // These imports are restricted elsewhere to ensure consistent
3 | // usage of typed hooks throughout the application.
4 | // We disable the ESLint rule here because this is the designated place
5 | // for importing and re-exporting the typed versions of hooks.
6 | /* eslint-disable no-restricted-imports */
7 | import { useEffect } from "react"
8 | import { Animated, useAnimatedValue, useWindowDimensions } from "react-native"
9 | import { useDispatch, useSelector } from "react-redux"
10 | import type { AppDispatch, RootState } from "./store"
11 |
12 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
13 | export const useAppDispatch = useDispatch.withTypes()
14 | export const useAppSelector = useSelector.withTypes()
15 |
16 | /**
17 | * Custom React hook for calculating viewport units
18 | * based on the current window dimensions.
19 | *
20 | * @returns An object containing the calculated viewport heigh and width values.
21 | */
22 | export const useViewportUnits = (): { vh: number; vw: number } => {
23 | const { width, height } = useWindowDimensions()
24 |
25 | const vh = height / 100
26 | const vw = width / 100
27 |
28 | return { vh, vw }
29 | }
30 |
31 | /**
32 | * Custom React hook for creating a bounce animation effect.
33 | *
34 | * @param value - The maximum height to which the object should bounce. Defaults to 10 if not provided.
35 | * @returns The {@linkcode Animated.Value} object that can be used to drive animations.
36 | */
37 | export const useBounceAnimation = (value = 10): Animated.Value => {
38 | const bounce = useAnimatedValue(0)
39 |
40 | bounce.interpolate({
41 | inputRange: [-300, -100, 0, 100, 101],
42 | outputRange: [300, 0, 1, 0, 0],
43 | })
44 |
45 | useEffect(() => {
46 | Animated.loop(
47 | Animated.sequence([
48 | Animated.timing(bounce, {
49 | toValue: value,
50 | duration: 1500,
51 | useNativeDriver: true,
52 | }),
53 | Animated.timing(bounce, {
54 | toValue: 0,
55 | duration: 1500,
56 | useNativeDriver: true,
57 | }),
58 | ]),
59 | ).start()
60 | }, [bounce, value])
61 |
62 | return bounce
63 | }
64 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import type { Action, ThunkAction } from "@reduxjs/toolkit"
2 | import { combineSlices, configureStore } from "@reduxjs/toolkit"
3 | import { setupListeners } from "@reduxjs/toolkit/query"
4 | import { counterSlice } from "../features/counter/counterSlice"
5 | import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
6 |
7 | // `combineSlices` automatically combines the reducers using
8 | // their `reducerPath`s, therefore we no longer need to call `combineReducers`.
9 | const rootReducer = combineSlices(counterSlice, quotesApiSlice)
10 | // Infer the `RootState` type from the root reducer
11 | export type RootState = ReturnType
12 |
13 | // The store setup is wrapped in `makeStore` to allow reuse
14 | // when setting up tests that need the same store config
15 | export const makeStore = (preloadedState?: Partial) => {
16 | const store = configureStore({
17 | reducer: rootReducer,
18 | // Adding the api middleware enables caching, invalidation, polling,
19 | // and other useful features of `rtk-query`.
20 | middleware: getDefaultMiddleware => {
21 | return getDefaultMiddleware().concat(quotesApiSlice.middleware)
22 | },
23 | preloadedState,
24 | })
25 | // configure listeners using the provided defaults
26 | // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
27 | setupListeners(store.dispatch)
28 | return store
29 | }
30 |
31 | export const store = makeStore()
32 |
33 | // Infer the type of `store`
34 | export type AppStore = typeof store
35 | // Infer the `AppDispatch` type from the store itself
36 | export type AppDispatch = AppStore["dispatch"]
37 | export type AppThunk = ThunkAction<
38 | ThunkReturnType,
39 | RootState,
40 | unknown,
41 | Action
42 | >
43 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/components/AsyncButton.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX, PropsWithChildren } from "react"
2 | import type {
3 | GestureResponderEvent,
4 | PressableProps,
5 | ViewStyle,
6 | } from "react-native"
7 | import {
8 | Animated,
9 | Pressable,
10 | StyleSheet,
11 | View,
12 | useAnimatedValue,
13 | } from "react-native"
14 |
15 | type AsyncButtonProps = PressableProps & PropsWithChildren
16 |
17 | export const AsyncButton = ({
18 | onPress,
19 | style,
20 | children,
21 | ...restProps
22 | }: AsyncButtonProps): JSX.Element => {
23 | const progress = useAnimatedValue(0)
24 | const opacity = useAnimatedValue(1)
25 |
26 | const _onPress = (e: GestureResponderEvent) => {
27 | progress.setValue(0)
28 | opacity.setValue(1)
29 |
30 | onPress?.(e)
31 |
32 | Animated.timing(progress, {
33 | toValue: 1,
34 | duration: 1000,
35 | useNativeDriver: false,
36 | }).start(({ finished }) => {
37 | if (!finished) {
38 | return
39 | }
40 |
41 | Animated.timing(opacity, {
42 | toValue: 0,
43 | duration: 200,
44 | useNativeDriver: false,
45 | }).start()
46 | })
47 | }
48 |
49 | const progressInterpolate = progress.interpolate({
50 | inputRange: [0, 1],
51 | outputRange: ["0%", "100%"],
52 | extrapolate: "clamp",
53 | })
54 |
55 | const progressStyle: Animated.WithAnimatedObject = {
56 | width: progressInterpolate,
57 | opacity,
58 | }
59 |
60 | return (
61 |
62 |
63 |
64 |
65 | {children}
66 |
67 | )
68 | }
69 |
70 | const styles = StyleSheet.create({
71 | progress: {
72 | position: "absolute",
73 | top: 0,
74 | bottom: 0,
75 | left: 0,
76 | backgroundColor: "rgba(112,76,182, 0.15)",
77 | },
78 | })
79 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { Animated, StyleSheet, View, useColorScheme } from "react-native"
3 | import { useBounceAnimation, useViewportUnits } from "../app/hooks"
4 | import { TypedColors } from "../constants/TypedColors"
5 | import logo from "./logo.gif"
6 |
7 | export const Header = (): JSX.Element => {
8 | const isDarkMode = useColorScheme() === "dark"
9 | const { vh } = useViewportUnits()
10 | const bounce = useBounceAnimation()
11 | const height = 40 * vh
12 |
13 | return (
14 |
20 |
25 |
26 | )
27 | }
28 |
29 | const styles = StyleSheet.create({
30 | container: {
31 | flexDirection: "row",
32 | justifyContent: "center",
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/components/Section.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX, PropsWithChildren } from "react"
2 | import { StyleSheet, Text, View, useColorScheme } from "react-native"
3 | import { TypedColors } from "../constants/TypedColors"
4 |
5 | type SectionProps = PropsWithChildren<{
6 | title: string
7 | }>
8 |
9 | export const Section = ({ children, title }: SectionProps): JSX.Element => {
10 | const isDarkMode = useColorScheme() === "dark"
11 |
12 | return (
13 |
14 |
20 | {title}
21 |
22 |
28 | {children}
29 |
30 |
31 | )
32 | }
33 |
34 | const styles = StyleSheet.create({
35 | sectionContainer: {
36 | marginTop: 32,
37 | paddingHorizontal: 24,
38 | },
39 | sectionTitle: {
40 | fontSize: 24,
41 | fontWeight: "600",
42 | },
43 | sectionDescription: {
44 | marginTop: 8,
45 | fontSize: 18,
46 | fontWeight: "400",
47 | },
48 | })
49 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/components/logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/redux-templates/3fea92b2b899f457995032a52fc9d92e13a4976f/packages/react-native-template-redux-typescript/template/src/components/logo.gif
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/constants/TypedColors.ts:
--------------------------------------------------------------------------------
1 | import { Colors } from "react-native/Libraries/NewAppScreen"
2 |
3 | type AllColors = {
4 | primary: string
5 | white: string
6 | lighter: string
7 | light: string
8 | dark: string
9 | darker: string
10 | black: string
11 | }
12 |
13 | export const TypedColors: AllColors = Colors satisfies AllColors as AllColors
14 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1): Promise<{ data: number }> =>
3 | new Promise<{ data: number }>(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/features/counter/counterSlice.test.ts:
--------------------------------------------------------------------------------
1 | import { makeStore } from "../../app/store"
2 | import type { CounterSliceState } from "./counterSlice"
3 | import {
4 | counterSlice,
5 | decrement,
6 | increment,
7 | incrementByAmount,
8 | selectCount,
9 | } from "./counterSlice"
10 |
11 | describe("counter reducer", () => {
12 | const initialState: CounterSliceState = {
13 | value: 3,
14 | status: "idle",
15 | }
16 |
17 | let store = makeStore()
18 |
19 | beforeEach(() => {
20 | store = makeStore({ counter: initialState })
21 | })
22 |
23 | it("should handle initial state", () => {
24 | expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
25 | value: 0,
26 | status: "idle",
27 | })
28 | })
29 |
30 | it("should handle increment", () => {
31 | expect(selectCount(store.getState())).toBe(3)
32 |
33 | store.dispatch(increment())
34 |
35 | expect(selectCount(store.getState())).toBe(4)
36 | })
37 |
38 | it("should handle decrement", () => {
39 | expect(selectCount(store.getState())).toBe(3)
40 |
41 | store.dispatch(decrement())
42 |
43 | expect(selectCount(store.getState())).toBe(2)
44 | })
45 |
46 | it("should handle incrementByAmount", () => {
47 | expect(selectCount(store.getState())).toBe(3)
48 |
49 | store.dispatch(incrementByAmount(2))
50 |
51 | expect(selectCount(store.getState())).toBe(5)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/src/features/quotes/quotesApiSlice.ts:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import `createApi`
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
3 |
4 | type Quote = {
5 | id: number
6 | quote: string
7 | author: string
8 | }
9 |
10 | type QuotesApiResponse = {
11 | quotes: Quote[]
12 | total: number
13 | skip: number
14 | limit: number
15 | }
16 |
17 | // Define a service using a base URL and expected endpoints
18 | export const quotesApiSlice = createApi({
19 | baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
20 | reducerPath: "quotesApi",
21 | // Tag types are used for caching and invalidation.
22 | tagTypes: ["Quotes"],
23 | endpoints: build => ({
24 | // Supply generics for the return type (in this case `QuotesApiResponse`)
25 | // and the expected query argument. If there is no argument, use `void`
26 | // for the argument type instead.
27 | getQuotes: build.query({
28 | query: (limit = 10) => `?limit=${limit.toString()}`,
29 | // `providesTags` determines which 'tag' is attached to the
30 | // cached data returned by the query.
31 | providesTags: (_result, _error, id) => [{ type: "Quotes", id }],
32 | }),
33 | }),
34 | })
35 |
36 | // Hooks are auto-generated by RTK-Query
37 | // Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
38 | export const { useGetQuotesQuery } = quotesApiSlice
39 |
--------------------------------------------------------------------------------
/packages/react-native-template-redux-typescript/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@react-native/typescript-config/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | typesversions
27 | .cache
28 | .yarnrc
29 | .yarn/*
30 | !.yarn/patches
31 | !.yarn/releases
32 | !.yarn/plugins
33 | !.yarn/sdks
34 | !.yarn/versions
35 | .pnp.*
36 | *.tgz
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/README.md:
--------------------------------------------------------------------------------
1 | # Redux Toolkit App Structure Example
2 |
3 | This slimmed-down version of [the Redux Toolkit template for Vite](https://github.com/reduxjs/redux-templates/tree/master/packages/vite-template-redux) shows the standard app structure and store setup for Redux Toolkit, TypeScript, and React.
4 |
5 | It's used in the ["Redux Essentials" tutorial Part 2](https://redux.js.org/tutorials/essentials/part-2-app-structure) as an embeddable CodeSandbox to help teach Redux app structure.
6 |
7 | ## Scripts
8 |
9 | - `dev`/`start` - start dev server and open browser
10 | - `build` - build for production
11 | - `preview` - locally preview production build
12 |
13 | ## Inspiration
14 |
15 | - [Create React App](https://github.com/facebook/create-react-app/tree/main/packages/cra-template)
16 | - [Vite](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react)
17 | - [Vitest](https://github.com/vitest-dev/vitest/tree/main/examples/react-testing-lib)
18 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Redux App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rtk-app-structure-example",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "start": "vite",
8 | "build": "tsc && vite build",
9 | "type-check": "tsc --noEmit"
10 | },
11 | "dependencies": {
12 | "@reduxjs/toolkit": "^2.6.1",
13 | "react": "^19.1.0",
14 | "react-dom": "^19.1.0",
15 | "react-redux": "^9.2.0"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^22.14.0",
19 | "@types/react": "^19.1.0",
20 | "@types/react-dom": "^19.1.1",
21 | "@vitejs/plugin-react": "^4.3.4",
22 | "prettier": "^3.5.3",
23 | "typescript": "^5.8.2",
24 | "vite": "^6.2.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 20vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-float infinite 3s ease-in-out;
13 | }
14 | }
15 |
16 | .App-header {
17 | min-height: 100vh;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | justify-content: flex-start;
22 | font-size: calc(10px + 2vmin);
23 | }
24 |
25 | .App-link {
26 | color: rgb(112, 76, 182);
27 | }
28 |
29 | @keyframes App-logo-float {
30 | 0% {
31 | transform: translateY(0);
32 | }
33 | 50% {
34 | transform: translateY(10px);
35 | }
36 | 100% {
37 | transform: translateY(0px);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import "./App.css"
3 | import { Counter } from "./features/counter/Counter"
4 | import logo from "./logo.svg"
5 |
6 | export const App = (): JSX.Element => (
7 |
36 | )
37 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | import { useDispatch, useSelector } from "react-redux"
3 | import type { AppDispatch, RootState } from "./store"
4 |
5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
6 | export const useAppDispatch = useDispatch.withTypes()
7 | export const useAppSelector = useSelector.withTypes()
8 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import type { Action, ThunkAction } from "@reduxjs/toolkit"
2 | import { configureStore } from "@reduxjs/toolkit"
3 | import counterReducer from "@/features/counter/counterSlice"
4 |
5 | export const store = configureStore({
6 | reducer: {
7 | counter: counterReducer,
8 | },
9 | })
10 |
11 | // Infer the type of `store`
12 | export type AppStore = typeof store
13 | export type RootState = ReturnType
14 | // Infer the `AppDispatch` type from the store itself
15 | export type AppDispatch = AppStore["dispatch"]
16 | // Define a reusable type describing thunk functions
17 | export type AppThunk = ThunkAction<
18 | ThunkReturnType,
19 | RootState,
20 | unknown,
21 | Action
22 | >
23 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/features/counter/Counter.module.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 | .row > button {
8 | margin-left: 4px;
9 | margin-right: 8px;
10 | }
11 |
12 | .row:not(:last-child) {
13 | margin-bottom: 16px;
14 | }
15 |
16 | .value {
17 | font-size: 78px;
18 | padding-left: 16px;
19 | padding-right: 16px;
20 | margin-top: 2px;
21 | font-family: "Courier New", Courier, monospace;
22 | }
23 |
24 | .button {
25 | appearance: none;
26 | background: none;
27 | font-size: 32px;
28 | padding-left: 12px;
29 | padding-right: 12px;
30 | outline: none;
31 | border: 2px solid transparent;
32 | color: rgb(112, 76, 182);
33 | padding-bottom: 4px;
34 | cursor: pointer;
35 | background-color: rgba(112, 76, 182, 0.1);
36 | border-radius: 2px;
37 | transition: all 0.15s;
38 | }
39 |
40 | .textbox {
41 | font-size: 32px;
42 | padding: 2px;
43 | width: 64px;
44 | text-align: center;
45 | margin-right: 4px;
46 | }
47 |
48 | .button:hover,
49 | .button:focus {
50 | border: 2px solid rgba(112, 76, 182, 0.4);
51 | }
52 |
53 | .button:active {
54 | background-color: rgba(112, 76, 182, 0.2);
55 | }
56 |
57 | .oddButton {
58 | composes: button;
59 | font-size: 24px;
60 | }
61 |
62 | .asyncButton {
63 | composes: button;
64 | font-size: 24px;
65 | position: relative;
66 | }
67 |
68 | .asyncButton:after {
69 | content: "";
70 | background-color: rgba(112, 76, 182, 0.15);
71 | display: block;
72 | position: absolute;
73 | width: 100%;
74 | height: 100%;
75 | left: 0;
76 | top: 0;
77 | opacity: 0;
78 | transition:
79 | width 1s linear,
80 | opacity 0.5s ease 1s;
81 | }
82 |
83 | .asyncButton:active:after {
84 | width: 0%;
85 | opacity: 1;
86 | transition: 0s;
87 | }
88 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/features/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | // Use pre-typed versions of the React-Redux
4 | // `useDispatch` and `useSelector` hooks
5 | import { useAppDispatch, useAppSelector } from "@/app/hooks"
6 | import type { JSX } from "react"
7 | import styles from "./Counter.module.css"
8 | import {
9 | decrement,
10 | increment,
11 | incrementAsync,
12 | incrementByAmount,
13 | incrementIfOdd,
14 | selectCount,
15 | selectStatus,
16 | } from "./counterSlice"
17 |
18 | export const Counter = (): JSX.Element => {
19 | const dispatch = useAppDispatch()
20 | const count = useAppSelector(selectCount)
21 | const status = useAppSelector(selectStatus)
22 | const [incrementAmount, setIncrementAmount] = useState("2")
23 |
24 | const incrementValue = Number(incrementAmount) || 0
25 |
26 | return (
27 |
28 |
29 |
38 |
39 | {count}
40 |
41 |
50 |
51 |
52 | {
58 | setIncrementAmount(e.target.value)
59 | }}
60 | />
61 |
69 |
70 |
71 |
80 |
88 |
89 |
90 | )
91 | }
92 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1): Promise<{ data: number }> =>
3 | new Promise<{ data: number }>(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family:
4 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family:
12 | source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 | import { Provider } from "react-redux"
4 | import { App } from "./App"
5 | import { store } from "./app/store"
6 | import "./index.css"
7 |
8 | const container = document.getElementById("root")!
9 | const root = createRoot(container)
10 |
11 | root.render(
12 |
13 |
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "module": "ESNext",
12 | "moduleResolution": "bundler",
13 | "isolatedModules": true,
14 | "resolveJsonModule": true,
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 | "baseUrl": ".",
18 | "paths": {
19 | "@/*": ["./src/*"]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/rtk-app-structure-example/vite.config.mts:
--------------------------------------------------------------------------------
1 | import path from "path"
2 | import { defineConfig } from "vite"
3 | import react from "@vitejs/plugin-react"
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 | resolve: {
9 | alias: [{ find: "@", replacement: path.resolve("./src") }],
10 | },
11 | server: {
12 | open: true,
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | .yalc/
27 | yalc.lock
28 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/README.md:
--------------------------------------------------------------------------------
1 | # vite-template-redux
2 |
3 | Uses [Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), and [React Testing Library](https://github.com/testing-library/react-testing-library) to create a modern [React](https://react.dev/) app compatible with [Create React App](https://create-react-app.dev/)
4 |
5 | ```sh
6 | npx tiged reduxjs/redux-templates/packages/vite-template-redux my-app
7 | ```
8 |
9 | ## Goals
10 |
11 | - Easy migration from Create React App or Vite
12 | - As beginner friendly as Create React App
13 | - Optimized performance compared to Create React App
14 | - Customizable without ejecting
15 |
16 | ## Scripts
17 |
18 | - `dev`/`start` - start dev server and open browser
19 | - `build` - build for production
20 | - `preview` - locally preview production build
21 | - `test` - launch test runner
22 |
23 | ## Inspiration
24 |
25 | - [Create React App](https://github.com/facebook/create-react-app/tree/main/packages/cra-template)
26 | - [Vite](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react)
27 | - [Vitest](https://github.com/vitest-dev/vitest/tree/main/examples/react-testing-lib)
28 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import vitestPlugin from "@vitest/eslint-plugin"
3 | import prettierConfig from "eslint-config-prettier/flat"
4 | import reactPlugin from "eslint-plugin-react"
5 | import reactHooksPlugin from "eslint-plugin-react-hooks"
6 | import globals from "globals"
7 | import { config, configs } from "typescript-eslint"
8 |
9 | const eslintConfig = config(
10 | {
11 | name: "global-ignores",
12 | ignores: [
13 | "**/*.snap",
14 | "**/dist/",
15 | "**/.yalc/",
16 | "**/build/",
17 | "**/temp/",
18 | "**/.temp/",
19 | "**/.tmp/",
20 | "**/.yarn/",
21 | "**/coverage/",
22 | ],
23 | },
24 | {
25 | name: `${js.meta.name}/recommended`,
26 | ...js.configs.recommended,
27 | },
28 | configs.strictTypeChecked,
29 | configs.stylisticTypeChecked,
30 | vitestPlugin.configs.recommended,
31 | {
32 | name: "eslint-plugin-react/jsx-runtime",
33 | ...reactPlugin.configs.flat["jsx-runtime"],
34 | },
35 | reactHooksPlugin.configs["recommended-latest"],
36 | {
37 | name: "main",
38 | linterOptions: {
39 | reportUnusedDisableDirectives: 2,
40 | },
41 | languageOptions: {
42 | ecmaVersion: 2020,
43 | globals: globals.browser,
44 | parserOptions: {
45 | projectService: true,
46 | tsconfigRootDir: import.meta.dirname,
47 | },
48 | },
49 | settings: {
50 | vitest: {
51 | typecheck: true,
52 | },
53 | },
54 | rules: {
55 | "no-undef": [0],
56 | "@typescript-eslint/consistent-type-definitions": [2, "type"],
57 | "@typescript-eslint/consistent-type-imports": [
58 | 2,
59 | {
60 | prefer: "type-imports",
61 | fixStyle: "separate-type-imports",
62 | disallowTypeAnnotations: true,
63 | },
64 | ],
65 | "no-restricted-imports": [
66 | 2,
67 | {
68 | paths: [
69 | {
70 | name: "react-redux",
71 | importNames: ["useSelector", "useStore", "useDispatch"],
72 | message:
73 | "Please use pre-typed versions from `src/app/hooks.ts` instead.",
74 | },
75 | ],
76 | },
77 | ],
78 | },
79 | },
80 |
81 | prettierConfig,
82 | )
83 |
84 | export default eslintConfig
85 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Redux App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-template-redux",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc -b && vite build",
8 | "dev": "vite",
9 | "format:check": "prettier --check .",
10 | "format": "prettier --write .",
11 | "lint:fix": "eslint --fix .",
12 | "lint": "eslint .",
13 | "preview": "vite preview",
14 | "start": "vite",
15 | "test": "vitest --run",
16 | "type-check": "tsc -b --noEmit"
17 | },
18 | "dependencies": {
19 | "@reduxjs/toolkit": "^2.6.1",
20 | "react": "^19.1.0",
21 | "react-dom": "^19.1.0",
22 | "react-redux": "^9.2.0"
23 | },
24 | "devDependencies": {
25 | "@eslint/js": "^9.23.0",
26 | "@testing-library/dom": "^10.4.0",
27 | "@testing-library/jest-dom": "^6.6.3",
28 | "@testing-library/react": "^16.3.0",
29 | "@testing-library/user-event": "^14.6.1",
30 | "@types/node": "^22.14.0",
31 | "@types/react": "^19.1.0",
32 | "@types/react-dom": "^19.1.1",
33 | "@vitejs/plugin-react": "^4.3.4",
34 | "@vitest/eslint-plugin": "^1.1.39",
35 | "eslint": "^9.23.0",
36 | "eslint-config-prettier": "^10.1.1",
37 | "eslint-plugin-prettier": "^5.2.6",
38 | "eslint-plugin-react": "^7.37.4",
39 | "eslint-plugin-react-hooks": "^5.2.0",
40 | "globals": "^16.0.0",
41 | "jsdom": "^26.0.0",
42 | "prettier": "^3.5.3",
43 | "typescript": "^5.8.2",
44 | "typescript-eslint": "^8.29.0",
45 | "vite": "^6.2.4",
46 | "vitest": "^3.1.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-float infinite 3s ease-in-out;
13 | }
14 | }
15 |
16 | .App-header {
17 | min-height: 100vh;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | justify-content: center;
22 | font-size: calc(10px + 2vmin);
23 | }
24 |
25 | .App-link {
26 | color: rgb(112, 76, 182);
27 | }
28 |
29 | @keyframes App-logo-float {
30 | 0% {
31 | transform: translateY(0);
32 | }
33 | 50% {
34 | transform: translateY(10px);
35 | }
36 | 100% {
37 | transform: translateY(0px);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css"
2 | import { Counter } from "./features/counter/Counter"
3 | import { Quotes } from "./features/quotes/Quotes"
4 | import logo from "./logo.svg"
5 |
6 | export const App = () => (
7 |
64 | )
65 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/app/createAppSlice.ts:
--------------------------------------------------------------------------------
1 | import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2 |
3 | // `buildCreateSlice` allows us to create a slice with async thunks.
4 | export const createAppSlice = buildCreateSlice({
5 | creators: { asyncThunk: asyncThunkCreator },
6 | })
7 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | // These imports are restricted elsewhere to ensure consistent
3 | // usage of typed hooks throughout the application.
4 | // We disable the ESLint rule here because this is the designated place
5 | // for importing and re-exporting the typed versions of hooks.
6 | /* eslint-disable no-restricted-imports */
7 | import { useDispatch, useSelector } from "react-redux"
8 | import type { AppDispatch, RootState } from "./store"
9 |
10 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
11 | export const useAppDispatch = useDispatch.withTypes()
12 | export const useAppSelector = useSelector.withTypes()
13 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import type { Action, ThunkAction } from "@reduxjs/toolkit"
2 | import { combineSlices, configureStore } from "@reduxjs/toolkit"
3 | import { setupListeners } from "@reduxjs/toolkit/query"
4 | import { counterSlice } from "../features/counter/counterSlice"
5 | import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
6 |
7 | // `combineSlices` automatically combines the reducers using
8 | // their `reducerPath`s, therefore we no longer need to call `combineReducers`.
9 | const rootReducer = combineSlices(counterSlice, quotesApiSlice)
10 | // Infer the `RootState` type from the root reducer
11 | export type RootState = ReturnType
12 |
13 | // The store setup is wrapped in `makeStore` to allow reuse
14 | // when setting up tests that need the same store config
15 | export const makeStore = (preloadedState?: Partial) => {
16 | const store = configureStore({
17 | reducer: rootReducer,
18 | // Adding the api middleware enables caching, invalidation, polling,
19 | // and other useful features of `rtk-query`.
20 | middleware: getDefaultMiddleware => {
21 | return getDefaultMiddleware().concat(quotesApiSlice.middleware)
22 | },
23 | preloadedState,
24 | })
25 | // configure listeners using the provided defaults
26 | // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
27 | setupListeners(store.dispatch)
28 | return store
29 | }
30 |
31 | export const store = makeStore()
32 |
33 | // Infer the type of `store`
34 | export type AppStore = typeof store
35 | // Infer the `AppDispatch` type from the store itself
36 | export type AppDispatch = AppStore["dispatch"]
37 | export type AppThunk = ThunkAction<
38 | ThunkReturnType,
39 | RootState,
40 | unknown,
41 | Action
42 | >
43 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/counter/Counter.module.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 | .row > button {
8 | margin-left: 4px;
9 | margin-right: 8px;
10 | }
11 |
12 | .row:not(:last-child) {
13 | margin-bottom: 16px;
14 | }
15 |
16 | .value {
17 | font-size: 78px;
18 | padding-left: 16px;
19 | padding-right: 16px;
20 | margin-top: 2px;
21 | font-family: "Courier New", Courier, monospace;
22 | }
23 |
24 | .button {
25 | appearance: none;
26 | background: none;
27 | font-size: 32px;
28 | padding-left: 12px;
29 | padding-right: 12px;
30 | outline: none;
31 | border: 2px solid transparent;
32 | color: rgb(112, 76, 182);
33 | padding-bottom: 4px;
34 | cursor: pointer;
35 | background-color: rgba(112, 76, 182, 0.1);
36 | border-radius: 2px;
37 | transition: all 0.15s;
38 | }
39 |
40 | .textbox {
41 | font-size: 32px;
42 | padding: 2px;
43 | width: 64px;
44 | text-align: center;
45 | margin-right: 4px;
46 | }
47 |
48 | .button:hover,
49 | .button:focus {
50 | border: 2px solid rgba(112, 76, 182, 0.4);
51 | }
52 |
53 | .button:active {
54 | background-color: rgba(112, 76, 182, 0.2);
55 | }
56 |
57 | .asyncButton {
58 | composes: button;
59 | position: relative;
60 | }
61 |
62 | .asyncButton:after {
63 | content: "";
64 | background-color: rgba(112, 76, 182, 0.15);
65 | display: block;
66 | position: absolute;
67 | width: 100%;
68 | height: 100%;
69 | left: 0;
70 | top: 0;
71 | opacity: 0;
72 | transition:
73 | width 1s linear,
74 | opacity 0.5s ease 1s;
75 | }
76 |
77 | .asyncButton:active:after {
78 | width: 0%;
79 | opacity: 1;
80 | transition: 0s;
81 | }
82 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { useState } from "react"
3 | import { useAppDispatch, useAppSelector } from "../../app/hooks"
4 | import styles from "./Counter.module.css"
5 | import {
6 | decrement,
7 | increment,
8 | incrementAsync,
9 | incrementByAmount,
10 | incrementIfOdd,
11 | selectCount,
12 | selectStatus,
13 | } from "./counterSlice"
14 |
15 | export const Counter = (): JSX.Element => {
16 | const dispatch = useAppDispatch()
17 | const count = useAppSelector(selectCount)
18 | const status = useAppSelector(selectStatus)
19 | const [incrementAmount, setIncrementAmount] = useState("2")
20 |
21 | const incrementValue = Number(incrementAmount) || 0
22 |
23 | return (
24 |
25 |
26 |
33 |
36 |
43 |
44 |
45 | {
51 | setIncrementAmount(e.target.value)
52 | }}
53 | />
54 |
60 |
69 |
77 |
78 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export const fetchCount = (amount = 1): Promise<{ data: number }> =>
3 | new Promise<{ data: number }>(resolve =>
4 | setTimeout(() => {
5 | resolve({ data: amount })
6 | }, 500),
7 | )
8 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/counter/counterSlice.test.ts:
--------------------------------------------------------------------------------
1 | import type { AppStore } from "../../app/store"
2 | import { makeStore } from "../../app/store"
3 | import type { CounterSliceState } from "./counterSlice"
4 | import {
5 | counterSlice,
6 | decrement,
7 | increment,
8 | incrementByAmount,
9 | selectCount,
10 | } from "./counterSlice"
11 |
12 | type LocalTestContext = {
13 | store: AppStore
14 | }
15 |
16 | describe("counter reducer", () => {
17 | beforeEach(context => {
18 | const initialState: CounterSliceState = {
19 | value: 3,
20 | status: "idle",
21 | }
22 |
23 | const store = makeStore({ counter: initialState })
24 |
25 | context.store = store
26 | })
27 |
28 | it("should handle initial state", () => {
29 | expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
30 | value: 0,
31 | status: "idle",
32 | })
33 | })
34 |
35 | it("should handle increment", ({ store }) => {
36 | expect(selectCount(store.getState())).toBe(3)
37 |
38 | store.dispatch(increment())
39 |
40 | expect(selectCount(store.getState())).toBe(4)
41 | })
42 |
43 | it("should handle decrement", ({ store }) => {
44 | expect(selectCount(store.getState())).toBe(3)
45 |
46 | store.dispatch(decrement())
47 |
48 | expect(selectCount(store.getState())).toBe(2)
49 | })
50 |
51 | it("should handle incrementByAmount", ({ store }) => {
52 | expect(selectCount(store.getState())).toBe(3)
53 |
54 | store.dispatch(incrementByAmount(2))
55 |
56 | expect(selectCount(store.getState())).toBe(5)
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/quotes/Quotes.module.css:
--------------------------------------------------------------------------------
1 | .select {
2 | font-size: 25px;
3 | padding: 5px;
4 | padding-top: 2px;
5 | padding-bottom: 2px;
6 | size: 50;
7 | outline: none;
8 | border: 2px solid transparent;
9 | color: rgb(112, 76, 182);
10 | cursor: pointer;
11 | background-color: rgba(112, 76, 182, 0.1);
12 | border-radius: 5px;
13 | transition: all 0.15s;
14 | }
15 |
16 | .container {
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/quotes/Quotes.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from "react"
2 | import { useState } from "react"
3 | import styles from "./Quotes.module.css"
4 | import { useGetQuotesQuery } from "./quotesApiSlice"
5 |
6 | const options = [5, 10, 20, 30]
7 |
8 | export const Quotes = (): JSX.Element | null => {
9 | const [numberOfQuotes, setNumberOfQuotes] = useState(10)
10 | // Using a query hook automatically fetches data and returns query values
11 | const { data, isError, isLoading, isSuccess } =
12 | useGetQuotesQuery(numberOfQuotes)
13 |
14 | if (isError) {
15 | return (
16 |
17 |
There was an error!!!
18 |
19 | )
20 | }
21 |
22 | if (isLoading) {
23 | return (
24 |
25 |
Loading...
26 |
27 | )
28 | }
29 |
30 | if (isSuccess) {
31 | return (
32 |
33 |
Select the Quantity of Quotes to Fetch:
34 |
47 | {data.quotes.map(({ author, quote, id }) => (
48 |
49 | “{quote}”
50 |
53 |
54 | ))}
55 |
56 | )
57 | }
58 |
59 | return null
60 | }
61 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/features/quotes/quotesApiSlice.ts:
--------------------------------------------------------------------------------
1 | // Need to use the React-specific entry point to import `createApi`
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
3 |
4 | type Quote = {
5 | id: number
6 | quote: string
7 | author: string
8 | }
9 |
10 | type QuotesApiResponse = {
11 | quotes: Quote[]
12 | total: number
13 | skip: number
14 | limit: number
15 | }
16 |
17 | // Define a service using a base URL and expected endpoints
18 | export const quotesApiSlice = createApi({
19 | baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
20 | reducerPath: "quotesApi",
21 | // Tag types are used for caching and invalidation.
22 | tagTypes: ["Quotes"],
23 | endpoints: build => ({
24 | // Supply generics for the return type (in this case `QuotesApiResponse`)
25 | // and the expected query argument. If there is no argument, use `void`
26 | // for the argument type instead.
27 | getQuotes: build.query({
28 | query: (limit = 10) => `?limit=${limit.toString()}`,
29 | // `providesTags` determines which 'tag' is attached to the
30 | // cached data returned by the query.
31 | providesTags: (_result, _error, id) => [{ type: "Quotes", id }],
32 | }),
33 | }),
34 | })
35 |
36 | // Hooks are auto-generated by RTK-Query
37 | // Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
38 | export const { useGetQuotesQuery } = quotesApiSlice
39 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family:
4 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family:
12 | source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 | import { Provider } from "react-redux"
4 | import { App } from "./App"
5 | import { store } from "./app/store"
6 | import "./index.css"
7 |
8 | const container = document.getElementById("root")
9 |
10 | if (container) {
11 | const root = createRoot(container)
12 |
13 | root.render(
14 |
15 |
16 |
17 |
18 | ,
19 | )
20 | } else {
21 | throw new Error(
22 | "Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/jest-dom/vitest"
2 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 | "resolveJsonModule": true,
18 |
19 | /* Linting */
20 | "strict": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true,
25 | "types": ["vitest/globals"]
26 | },
27 | "include": ["src"]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "allowJs": true,
9 | "checkJs": true,
10 |
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "isolatedModules": true,
15 | "moduleDetection": "force",
16 | "noEmit": true,
17 | "resolveJsonModule": true,
18 |
19 | /* Linting */
20 | "strict": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true
25 | },
26 | "include": ["vite.config.ts", "eslint.config.js"]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/vite-template-redux/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react"
2 | import * as path from "node:path"
3 | import { defineConfig } from "vitest/config"
4 | import packageJson from "./package.json" with { type: "json" }
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [react()],
9 |
10 | server: {
11 | open: true,
12 | },
13 |
14 | test: {
15 | root: import.meta.dirname,
16 | name: packageJson.name,
17 | environment: "jsdom",
18 |
19 | typecheck: {
20 | enabled: true,
21 | tsconfig: path.join(import.meta.dirname, "tsconfig.json"),
22 | },
23 |
24 | globals: true,
25 | watch: false,
26 | setupFiles: ["./src/setupTests.ts"],
27 | },
28 | })
29 |
--------------------------------------------------------------------------------
/prettier.config.mjs:
--------------------------------------------------------------------------------
1 | /** @import { Config } from "prettier" */
2 |
3 | /**
4 | * @satisfies {Config}
5 | */
6 | const prettierConfig = {
7 | arrowParens: "avoid",
8 | semi: false,
9 | }
10 |
11 | export default prettierConfig
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "checkJs": true,
6 | "declaration": true,
7 | "esModuleInterop": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "isolatedModules": true,
10 | "jsx": "react-jsx",
11 | "lib": ["DOM", "ESNext"],
12 | "module": "ESNext",
13 | "moduleResolution": "bundler",
14 | "noEmit": true,
15 | "noErrorTruncation": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitOverride": true,
18 | "noImplicitReturns": true,
19 | "noUnusedLocals": false,
20 | "noUnusedParameters": false,
21 | "outDir": "./dist",
22 | "resolveJsonModule": true,
23 | "rootDir": "./",
24 | "skipLibCheck": true,
25 | "sourceMap": true,
26 | "strict": true,
27 | "target": "ESNext",
28 | "types": ["vitest/globals", "vitest/importMeta"],
29 | "useDefineForClassFields": true,
30 | "useUnknownInCatchVariables": true
31 | },
32 | "exclude": [
33 | "dist",
34 | "packages/cra-template-redux/template",
35 | "packages/cra-template-redux-typescript/template",
36 | "packages/expo-template-redux-typescript",
37 | "packages/react-native-template-redux-typescript/template",
38 | "packages/rtk-app-structure-example",
39 | "packages/vite-template-redux"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------