diff --git a/.changeset/thin-wolves-push.md b/.changeset/thin-wolves-push.md
new file mode 100644
index 00000000..56bbea77
--- /dev/null
+++ b/.changeset/thin-wolves-push.md
@@ -0,0 +1,5 @@
+---
+'create-expo-stack': minor
+---
+
+add redux
diff --git a/cli/src/commands/create-expo-stack.ts b/cli/src/commands/create-expo-stack.ts
index 16e14522..fca71ab0 100644
--- a/cli/src/commands/create-expo-stack.ts
+++ b/cli/src/commands/create-expo-stack.ts
@@ -309,6 +309,14 @@ const command: GluegunCommand = {
cliResults.packages.push({ name: 'vexo-analytics', type: 'analytics' });
}
+ if (options.redux) {
+ // Add redux package
+ cliResults.packages.push({
+ name: 'redux',
+ type: 'state-management'
+ });
+ }
+
// By this point, all cliResults should be set
info('');
highlight('Your project configuration:');
@@ -354,6 +362,13 @@ const command: GluegunCommand = {
script += '--drawer+tabs ';
}
}
+
+ const stateManagementPackage = cliResults.packages.find((p) => p.type === 'state-management');
+
+ if (stateManagementPackage) {
+ // currently only redux is supported
+ script += `--${stateManagementPackage.name} `;
+ }
} else {
// Add the packages
cliResults.packages.forEach((p) => {
@@ -407,6 +422,7 @@ const command: GluegunCommand = {
const stylingPackage = packages.find((p) => p.type === 'styling');
const internalizationPackage = packages.find((p) => p.type === 'internationalization');
const analyticsPackage = packages.find((p) => p.type === 'analytics');
+ const stateManagementPackage = packages.find((p) => p.type === 'state-management') || undefined;
let files: string[] = [];
@@ -418,7 +434,8 @@ const command: GluegunCommand = {
analyticsPackage,
toolbox,
cliResults,
- internalizationPackage
+ internalizationPackage,
+ stateManagementPackage
);
// Once all the files are defined, format and generate them
@@ -434,7 +451,8 @@ const command: GluegunCommand = {
packageManager,
stylingPackage,
toolbox,
- internalizationPackage
+ internalizationPackage,
+ stateManagementPackage
);
await printOutput(cliResults, formattedFiles, toolbox, stylingPackage);
diff --git a/cli/src/templates/base/App.tsx.ejs b/cli/src/templates/base/App.tsx.ejs
index 112a2feb..dd6e272f 100644
--- a/cli/src/templates/base/App.tsx.ejs
+++ b/cli/src/templates/base/App.tsx.ejs
@@ -1,5 +1,9 @@
import { ScreenContent } from 'components/ScreenContent';
import { StatusBar } from 'expo-status-bar';
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
<% if (props.internalizationPackage?.name === "i18next") { %>
import './translation';
@@ -28,15 +32,23 @@ import { StatusBar } from 'expo-status-bar';
vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY);
<% } %>
+
+
<% if (props.stylingPackage?.name === "restyle") {%>
export default function App() {
return (
-
- <% if (props.internalizationPackage?.name === "i18next") { %>
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.internalizationPackage?.name === "i18next") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
<% } %>
-
);
@@ -45,13 +57,19 @@ import { StatusBar } from 'expo-status-bar';
export default function App() {
return (
-
- <% if (props.internalizationPackage?.name === "i18next") { %>
-
- <% } else { %>
- Open up App.tsx to start working on your app!
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.internalizationPackage?.name === "i18next") { %>
+
+ <% } else { %>
+ Open up App.tsx to start working on your app!
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
<% } %>
-
);
@@ -60,11 +78,17 @@ import { StatusBar } from 'expo-status-bar';
export default function App() {
return (
<>
-
- <% if (props.internalizationPackage?.name === "i18next") { %>
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.internalizationPackage?.name === "i18next") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
<% } %>
-
>
);
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index e06d180e..43f77306 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -101,6 +101,11 @@
"vexo-analytics": "^1.3.15",
<% } %>
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+ "react-redux": "^9.1.2",
+ "@reduxjs/toolkit": "^2.2.7",
+ <% } %>
+
<% if ((props.navigationPackage?.name === "react-navigation") && (props.stylingPackage?.name === "tamagui")) { %>
"expo-splash-screen": "~0.27.4",
<% } %>
diff --git a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
index c5bf2d2a..a1f14e4f 100644
--- a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
@@ -36,6 +36,11 @@ import { Stack } from 'expo-router';
vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY);
<% } %>
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
+
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
@@ -65,10 +70,16 @@ export default function RootLayout() {
<% } %>
-
-
-
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+
+
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
diff --git a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
index 5220239c..237c62a3 100644
--- a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
@@ -27,6 +27,11 @@ import '../unistyles';
vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY);
<% } %>
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
+
export default function Layout() {
@@ -51,7 +56,13 @@ export default function Layout() {
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
diff --git a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
index 489ca9de..434aa0ec 100644
--- a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
@@ -33,6 +33,11 @@ import '../unistyles';
vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY);
<% } %>
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
+
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
@@ -61,10 +66,16 @@ export default function RootLayout() {
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
-
-
-
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+
+
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
diff --git a/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs
index 2972a438..81c11ae8 100644
--- a/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs
@@ -1,6 +1,10 @@
import '../global.css';
import 'expo-dev-client';
import { ThemeProvider as NavThemeProvider } from '@react-navigation/native';
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %>
import { ActionSheetProvider } from '@expo/react-native-action-sheet';
<% } %>
@@ -34,6 +38,10 @@ export default function RootLayout() {
/>
{/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */}
{/* */}
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %>
@@ -54,6 +62,9 @@ export default function RootLayout() {
<% } %>
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
{/* */}
>
);
diff --git a/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs
index 39d51068..59455142 100644
--- a/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs
@@ -2,6 +2,10 @@ import '../global.css';
import 'expo-dev-client';
import { ThemeProvider as NavThemeProvider } from '@react-navigation/native';
import { Icon } from '@roninoss/icons';
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %>
import { ActionSheetProvider } from '@expo/react-native-action-sheet';
<% } %>
@@ -37,6 +41,9 @@ export default function RootLayout() {
/>
{/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */}
{/* */}
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %>
@@ -57,6 +64,9 @@ export default function RootLayout() {
<% } %>
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
{/* */}
>
);
diff --git a/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs
index 9d8ef337..17a8077c 100644
--- a/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs
@@ -2,6 +2,10 @@ import '../global.css';
import 'expo-dev-client';
import { ThemeProvider as NavThemeProvider } from '@react-navigation/native';
import { Icon } from '@roninoss/icons';
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %>
import { ActionSheetProvider } from '@expo/react-native-action-sheet';
<% } %>
@@ -37,6 +41,9 @@ export default function RootLayout() {
/>
{/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */}
{/* */}
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
<% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %>
@@ -56,6 +63,9 @@ export default function RootLayout() {
<% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %>
+ <% } %>
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
<% } %>
{/* */}
>
diff --git a/cli/src/templates/packages/react-navigation/App.tsx.ejs b/cli/src/templates/packages/react-navigation/App.tsx.ejs
index 88baafae..16121a8a 100644
--- a/cli/src/templates/packages/react-navigation/App.tsx.ejs
+++ b/cli/src/templates/packages/react-navigation/App.tsx.ejs
@@ -34,6 +34,10 @@ import "react-native-gesture-handler";
<% } else { %>
import RootStack from "./navigation";
<% } %>
+<% if (props.stateManagementPackage?.name === "redux") { %>
+import { Provider } from 'react-redux'
+import store from 'store/store'
+<% } %>
export default function App() {
<% if (props.stylingPackage?.name === "tamagui") { %>
@@ -54,16 +58,36 @@ export default function App() {
return (
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
);
<% } else if (props.stylingPackage?.name === "restyle") { %>
return (
-
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
);
<% } else { %>
- return ;
+ return (
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+
+ <% if (props.stateManagementPackage?.name === "redux") { %>
+
+ <% } %>
+ );
<% } %>
}
diff --git a/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs b/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs
new file mode 100644
index 00000000..488f2d74
--- /dev/null
+++ b/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs
@@ -0,0 +1,23 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+interface InitialState {
+ demo: undefined;
+}
+
+const initialState: InitialState = {
+ demo: undefined,
+};
+
+export const demoSlice = createSlice({
+ name: 'demo',
+ initialState,
+ reducers: {
+ setDemo: (state, action) => {
+ state.demo = action.payload;
+ },
+ },
+});
+
+export const { setDemo } = demoSlice.actions;
+
+export default demoSlice.reducer;
\ No newline at end of file
diff --git a/cli/src/templates/packages/redux/store/store.ts.ejs b/cli/src/templates/packages/redux/store/store.ts.ejs
new file mode 100644
index 00000000..d6834078
--- /dev/null
+++ b/cli/src/templates/packages/redux/store/store.ts.ejs
@@ -0,0 +1,23 @@
+import { combineReducers, configureStore } from '@reduxjs/toolkit'
+import { useDispatch, useSelector } from 'react-redux'
+
+import demoReducer from './reducers/demoSlice'
+
+
+const rootReducer = combineReducers({
+ demo: demoReducer,
+})
+
+
+const store = configureStore({
+ reducer: rootReducer,
+})
+
+export default store
+
+export type RootState = ReturnType
+
+type AppDispatch = typeof store.dispatch
+
+export const useAppDispatch = useDispatch.withTypes()
+export const useAppSelector = useSelector.withTypes()
\ No newline at end of file
diff --git a/cli/src/types.ts b/cli/src/types.ts
index 48931900..ae30831d 100644
--- a/cli/src/types.ts
+++ b/cli/src/types.ts
@@ -26,7 +26,8 @@ export const availablePackages = [
'restyle',
'unistyles',
'i18next',
- 'vexo-analytics'
+ 'vexo-analytics',
+ 'redux'
] as const;
export type AuthenticationSelect = 'supabase' | 'firebase' | undefined;
@@ -43,6 +44,8 @@ export type Internalization = 'i18next';
export type Analytics = 'vexo-analytics';
+export type StateManagement = 'redux' | undefined;
+
export type SelectedComponents =
| 'action-sheet'
| 'activity-indicator'
@@ -59,7 +62,7 @@ export type SelectedComponents =
export type AvailablePackages = {
name: (typeof availablePackages)[number];
- type: 'navigation' | 'styling' | 'authentication' | 'internationalization' | 'analytics';
+ type: 'navigation' | 'styling' | 'authentication' | 'internationalization' | 'analytics' | 'state-management';
options?: { selectedComponents?: SelectedComponents[]; type?: NavigationTypes };
};
diff --git a/cli/src/utilities/configAnalytics.ts b/cli/src/utilities/configAnalytics.ts
index 40dad50a..f66eb258 100644
--- a/cli/src/utilities/configAnalytics.ts
+++ b/cli/src/utilities/configAnalytics.ts
@@ -9,6 +9,7 @@ import {
NavigationTypes,
PackageManager,
SelectedComponents,
+ StateManagement,
StylingSelect
} from '../types';
@@ -37,7 +38,8 @@ export async function storeConfigAnalytics({
osPlatform,
osArch,
osRelease,
- analytics
+ analytics,
+ stateManagement
}: {
timestamp: string;
cesVersion: string;
@@ -54,6 +56,7 @@ export async function storeConfigAnalytics({
osArch: string;
osRelease: string;
analytics: Analytics;
+ stateManagement?: StateManagement;
} & Partial) {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
console.log('Skipping analytics in development or test environment');
@@ -94,7 +97,8 @@ export async function storeConfigAnalytics({
osPlatform ?? '',
osArch ?? '',
osRelease ?? '',
- analytics ?? ''
+ analytics ?? '',
+ stateManagement ?? ''
]
]
},
diff --git a/cli/src/utilities/configureProjectFiles.ts b/cli/src/utilities/configureProjectFiles.ts
index 9f130d99..fc706ea8 100644
--- a/cli/src/utilities/configureProjectFiles.ts
+++ b/cli/src/utilities/configureProjectFiles.ts
@@ -8,6 +8,7 @@ import {
Internalization,
NavigationSelect,
NavigationTypes,
+ StateManagement,
StylingSelect
} from '../types';
import { getPackageManager, getVersionForPackageManager } from './getPackageManager';
@@ -21,7 +22,8 @@ export function configureProjectFiles(
analyticsPackage: AvailablePackages | undefined,
toolbox: Toolbox,
cliResults: CliResults,
- internalizationPackage: AvailablePackages | undefined
+ internalizationPackage: AvailablePackages | undefined,
+ stateManagementPackage: AvailablePackages | undefined
): string[] {
// Define the files common to all templates to be generated
let baseFiles = [
@@ -349,6 +351,13 @@ export function configureProjectFiles(
}
}
+ // add redux files if needed
+ if (stateManagementPackage?.name === 'redux') {
+ const reduxFiles = ['packages/redux/store/store.ts.ejs', 'packages/redux/store/reducers/demoSlice.ts.ejs'];
+
+ files = [...files, ...reduxFiles];
+ }
+
// Add npmrc file if user is using pnpm
if (packageManager === 'pnpm') {
files.push('base/.npmrc.ejs');
@@ -396,7 +405,8 @@ export function configureProjectFiles(
osPlatform: os.platform(),
osArch: os.arch(),
osRelease: os.release(),
- analytics: analyticsPackage?.name as Analytics
+ analytics: analyticsPackage?.name as Analytics,
+ stateManagement: stateManagementPackage?.name as StateManagement
});
return files;
diff --git a/cli/src/utilities/generateProjectFiles.ts b/cli/src/utilities/generateProjectFiles.ts
index 15dd1ad5..db504b40 100644
--- a/cli/src/utilities/generateProjectFiles.ts
+++ b/cli/src/utilities/generateProjectFiles.ts
@@ -11,7 +11,8 @@ export function generateProjectFiles(
packageManager: PackageManager,
stylingPackage: AvailablePackages | undefined,
toolbox: Toolbox,
- internalizationPackage: AvailablePackages | undefined
+ internalizationPackage: AvailablePackages | undefined,
+ stateManagementPackage: AvailablePackages | undefined
) {
const { projectName, packages, flags } = cliResults;
@@ -67,6 +68,10 @@ export function generateProjectFiles(
target = target.replace('packages/vexo-analytics/', '');
}
+ if (stateManagementPackage?.name === 'redux') {
+ target = target.replace('packages/redux/', '');
+ }
+
const gen = toolbox.template.generate({
template,
target: `./${projectName}/` + target,
@@ -79,7 +84,8 @@ export function generateProjectFiles(
packageManager,
packages,
stylingPackage,
- internalizationPackage
+ internalizationPackage,
+ stateManagementPackage
}
});
diff --git a/cli/src/utilities/runCLI.ts b/cli/src/utilities/runCLI.ts
index 6a7c6553..982fdbf6 100644
--- a/cli/src/utilities/runCLI.ts
+++ b/cli/src/utilities/runCLI.ts
@@ -10,7 +10,8 @@ import {
NavigationTypes,
PackageManager,
SelectedComponents,
- StylingSelect
+ StylingSelect,
+ StateManagement
} from '../types';
import { loadConfigs, saveConfig } from './configStorage';
import { getDefaultPackageManagerVersion } from './getPackageManager';
@@ -346,6 +347,25 @@ export async function runCLI(toolbox: Toolbox, projectName: string): Promise