Skip to content

Fix: [Section] Scalable Application Structure - #323

Closed
Ghostofcaldera wants to merge 1 commit into
piotrwitek:masterfrom
Ghostofcaldera:fix/issue-42-1781366689
Closed

Fix: [Section] Scalable Application Structure#323
Ghostofcaldera wants to merge 1 commit into
piotrwitek:masterfrom
Ghostofcaldera:fix/issue-42-1781366689

Conversation

@Ghostofcaldera

Copy link
Copy Markdown

Fixes #42

This PR addresses the IssueHunt-funded issue #42.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive guide on building a scalable, feature-based architecture for React-Redux applications. The reviewer provided valuable feedback on the code examples within the documentation, pointing out public API violations where internal paths are imported directly, type-safety risks associated with conditional store injection, and a lack of code splitting for disabled features. Actionable suggestions were provided to resolve these architectural issues.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +131 to +134
// Import reducers conditionally
import authReducer from '../features/auth/slices/authSlice';
import dashboardReducer from '../features/dashboard/slices/dashboardSlice';
import settingsReducer from '../features/settings/slices/settingsSlice';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Public API Violation

Importing directly from internal paths like ../features/auth/slices/authSlice bypasses the feature's public API (index.ts). This violates the encapsulation and modularity goals described earlier in the guide.

To maintain strict boundaries, always import from the feature's root entry point.

Suggested change
// Import reducers conditionally
import authReducer from '../features/auth/slices/authSlice';
import dashboardReducer from '../features/dashboard/slices/dashboardSlice';
import settingsReducer from '../features/settings/slices/settingsSlice';
// Import reducers conditionally from the public API of each feature
import { authReducer } from '../features/auth';
import { dashboardReducer } from '../features/dashboard';
import { settingsReducer } from '../features/settings';

Comment on lines +136 to +140
const reducer = {
...(FEATURE_FLAGS.AUTH && { auth: authReducer }),
...(FEATURE_FLAGS.DASHBOARD && { dashboard: dashboardReducer }),
...(FEATURE_FLAGS.SETTINGS && { settings: settingsReducer }),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Issues with Conditional Store Injection

Conditionally injecting reducers using object spreading can lead to runtime and type-safety issues:

  1. Type Safety: If RootState is inferred from store.getState(), the state slices for disabled features will be typed as optional or potentially undefined. If selectors are not written defensively (e.g., using optional chaining like state.auth?.user), the application will crash at runtime when a disabled feature's selector is invoked.
  2. Predictability: Redux store structure is generally expected to be static and predictable.

Recommended Alternatives:

  • Static Reducers with UI-only Toggling: Keep all reducers registered in the store. This ensures type safety and predictable state structure, while feature flags only control UI rendering and action dispatching.
  • Dynamic Reducer Injection: For large-scale applications where bundle size of reducers is a concern, use Redux Toolkit's dynamic reducer injection or a custom Reducer Manager to dynamically inject reducers when the feature is loaded.
Suggested change
const reducer = {
...(FEATURE_FLAGS.AUTH && { auth: authReducer }),
...(FEATURE_FLAGS.DASHBOARD && { dashboard: dashboardReducer }),
...(FEATURE_FLAGS.SETTINGS && { settings: settingsReducer }),
};
const reducer = {
auth: authReducer,
dashboard: dashboardReducer,
settings: settingsReducer,
};

Comment on lines +156 to +169
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 (
<div>
{FEATURE_FLAGS.AUTH && <AuthPage />}
{FEATURE_FLAGS.DASHBOARD && <Dashboard />}
{FEATURE_FLAGS.SETTINGS && <Settings />}
</div>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Public API Violation & Lack of Code Splitting

There are two main architectural issues with this implementation:

  1. Public API Violation: Importing directly from internal paths like ../features/auth/components/AuthPage bypasses the feature's public API (index.ts). The entry components should be exported from the feature's index.ts and imported from the feature root.
  2. Lack of Code Splitting: Statically importing all features means they will all be bundled together, even if their feature flags are disabled. Using React.lazy and Suspense ensures that disabled features are not loaded by the client, which is crucial for a scalable application.
Suggested change
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 (
<div>
{FEATURE_FLAGS.AUTH && <AuthPage />}
{FEATURE_FLAGS.DASHBOARD && <Dashboard />}
{FEATURE_FLAGS.SETTINGS && <Settings />}
</div>
);
}
import React, { Suspense, lazy } from 'react';
import { FEATURE_FLAGS } from '../config/featureFlags';
// Lazy load features from their public APIs to enable code-splitting
const AuthPage = lazy(() => import('../features/auth').then(m => ({ default: m.AuthPage })));
const Dashboard = lazy(() => import('../features/dashboard').then(m => ({ default: m.Dashboard })));
const Settings = lazy(() => import('../features/settings').then(m => ({ default: m.Settings })));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<div>
{FEATURE_FLAGS.AUTH && <AuthPage />}
{FEATURE_FLAGS.DASHBOARD && <Dashboard />}
{FEATURE_FLAGS.SETTINGS && <Settings />}
</div>
</Suspense>
);
}

@Ghostofcaldera

Copy link
Copy Markdown
Author

Closing as duplicate. The latest fix is in PR #338. Apologies for the noise — improving the automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Section] Scalable Application Structure

1 participant