diff --git a/docs/issue-42-[section]-scalable-application.md b/docs/issue-42-[section]-scalable-application.md new file mode 100644 index 00000000..bf11bdb5 --- /dev/null +++ b/docs/issue-42-[section]-scalable-application.md @@ -0,0 +1,226 @@ +# Scalable Application Structure + +Creating a scalable structure for a React-Redux application is crucial for long-term maintainability, team collaboration, and feature evolution. This section outlines a strategy focused on **feature-based architecture**, enabling you to group files by domain, easily add or remove features, toggle them on demand, and ensure reusability across different projects. + +## Core Goals + +1. **Feature-First Organization**: Group all related code (components, actions, reducers, selectors, types) by feature rather than by file type. +2. **Modularity**: Make features self-contained units that can be added or removed with minimal impact on the rest of the application. +3. **Dynamic Feature Toggling**: Enable or disable features at runtime or build time without refactoring core logic. +4. **Reusability & Pluggability**: Design features as independent packages or modules that can be dropped into different Redux applications. + +## Recommended Directory Structure + +Instead of the traditional `actions/`, `components/`, `reducers/` folders at the root, organize your `src` directory by **features**. + +```text +src/ +├── app/ # App-level configuration, providers, global styles +│ ├── store.ts # Redux store configuration +│ ├── App.tsx # Root component +│ └── index.tsx # Entry point +├── features/ # Feature modules +│ ├── auth/ # Authentication feature +│ │ ├── components/ # Auth-specific components +│ │ ├── slices/ # Redux Toolkit slice (actions + reducer) +│ │ ├── hooks.ts # Custom hooks for this feature +│ │ ├── selectors.ts # Feature-specific selectors +│ │ ├── types.ts # TypeScript interfaces/types +│ │ └── index.ts # Public API (exports only what's needed) +│ ├── dashboard/ # Dashboard feature +│ │ ├── components/ +│ │ ├── slices/ +│ │ ├── hooks.ts +│ │ ├── selectors.ts +│ │ ├── types.ts +│ │ └── index.ts +│ └── settings/ # Settings feature +│ └── ... +├── shared/ # Shared utilities, UI components, types +│ ├── components/ # Generic UI components (Button, Modal) +│ ├── hooks/ # Generic hooks +│ ├── utils/ # Helper functions +│ └── types.ts # Global types +└── config/ # Environment variables, feature flags + └── featureFlags.ts +``` + +## Implementation Details + +### 1. Feature Modules as Self-Contained Units + +Each feature in `src/features/` should be a complete unit. It should contain its own Redux slice, components, and types. This isolation ensures that changes to one feature rarely break another. + +**Example: `features/auth/slices/authSlice.ts`** + +```typescript +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface AuthState { + user: string | null; + isAuthenticated: boolean; + loading: boolean; +} + +const initialState: AuthState = { + user: null, + isAuthenticated: false, + loading: false, +}; + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + loginStart: (state) => { + state.loading = true; + }, + loginSuccess: (state, action: PayloadAction) => { + state.user = action.payload; + state.isAuthenticated = true; + state.loading = false; + }, + loginFailure: (state) => { + state.loading = false; + }, + logout: (state) => { + state.user = null; + state.isAuthenticated = false; + }, + }, +}); + +export const { loginStart, loginSuccess, loginFailure, logout } = authSlice.actions; +export default authSlice.reducer; +``` + +**Example: `features/auth/index.ts` (Public API)** + +```typescript +// Only export what external parts of the app need +export { default as authReducer } from './slices/authSlice'; +export { loginStart, loginSuccess, loginFailure, logout } from './slices/authSlice'; +export { useAuth } from './hooks'; +export { selectUser, selectIsAuthenticated } from './selectors'; +export type { AuthState } from './types'; +``` + +### 2. Enabling/Disabling Features on Demand + +To enable dynamic feature toggling, use a configuration file and conditional rendering or conditional store injection. + +**`src/config/featureFlags.ts`** + +```typescript +export const FEATURE_FLAGS = { + AUTH: process.env.REACT_APP_ENABLE_AUTH === 'true', + DASHBOARD: process.env.REACT_APP_ENABLE_DASHBOARD === 'true', + SETTINGS: process.env.REACT_APP_ENABLE_SETTINGS === 'true', +}; +``` + +**Conditional Store Injection** + +When configuring your Redux store, only include reducers for enabled features. + +```typescript +// src/app/store.ts +import { configureStore } from '@reduxjs/toolkit'; +import { FEATURE_FLAGS } from '../config/featureFlags'; + +// Import reducers conditionally +import authReducer from '../features/auth/slices/authSlice'; +import dashboardReducer from '../features/dashboard/slices/dashboardSlice'; +import settingsReducer from '../features/settings/slices/settingsSlice'; + +const reducer = { + ...(FEATURE_FLAGS.AUTH && { auth: authReducer }), + ...(FEATURE_FLAGS.DASHBOARD && { dashboard: dashboardReducer }), + ...(FEATURE_FLAGS.SETTINGS && { settings: settingsReducer }), +}; + +export const store = configureStore({ + reducer, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +``` + +**Conditional Component Rendering** + +In your main `App.tsx`, only render feature components if the flag is active. + +```typescript +// src/app/App.tsx +import { FEATURE_FLAGS } from '../config/featureFlags'; +import { AuthPage } from '../features/auth/components/AuthPage'; +import { Dashboard } from '../features/dashboard/components/Dashboard'; +import { Settings } from '../features/settings/components/Settings'; + +function App() { + return ( +
+ {FEATURE_FLAGS.AUTH && } + {FEATURE_FLAGS.DASHBOARD && } + {FEATURE_FLAGS.SETTINGS && } +
+ ); +} + +export default App; +``` + +### 3. Reusability and Pluggability + +To make features reusable across different applications: + +- **Package as a Library**: Extract a feature into a separate npm package. +- **Use TypeScript Interfaces**: Define clear contracts for props and state. +- **Avoid Hard Dependencies**: Do not import from other features directly. Use dependency injection or context if cross-feature communication is needed. +- **Export a Clean API**: Ensure the `index.ts` of each feature exports only the necessary components, hooks, and types. + +**Example: Exporting a Feature as a Reusable Module** + +```typescript +// features/auth/index.ts +export { AuthProvider, useAuth } from './components/AuthProvider'; +export { authReducer } from './slices/authSlice'; +export type { AuthState, AuthActions } from './types'; +``` + +Another application can then install this feature as a dependency: + +```bash +npm install @myapp/auth-feature +``` + +And use it: + +```typescript +import { AuthProvider, authReducer } from '@myapp/auth-feature'; + +// In store config +const store = configureStore({ + reducer: { + auth: authReducer, + // ... other reducers + }, +}); + +// In App + + + +``` + +## Benefits of This Structure + +- **Scalability**: As the app grows, new features are added without cluttering the root directory. +- **Maintainability**: Developers can work on a single feature without worrying about breaking others. +- **Flexibility**: Features can be toggled on/off via environment variables or runtime flags. +- **Reusability**: Features can be extracted and reused in other projects with minimal changes. + +## Conclusion + +Adopting a feature-based, scalable structure in your React-Redux TypeScript application ensures that your codebase remains clean, modular, and adaptable. By grouping files by feature, enabling dynamic toggling, and designing for reusability, you create a foundation that can grow with your project and team. \ No newline at end of file