Extending
Adding State Management

Adding a New State Management Library

Comprehensive guide to integrating additional global state solutions (e.g., MobX, Jotai, Recoil, Valtio) into React Kickstart alongside the existing Redux Toolkit and Zustand options.

Reference Implementations: Study the existing Redux Toolkit and Zustand implementations in src/features/state-management/ for patterns and conventions.


๐ŸŽฏ Integration Overview


๐Ÿ“‹ Complete Integration Guide

Declare Dependencies and Versions

File: src/builders/dependencies.js

Add your state management dependencies:

// MobX dependencies
export const mobx = {
  mobx: "^6.12.0",
  mobxReactLite: "^4.0.7",
  mobxStateTree: "^5.4.0" // Optional: for complex state structures
}
 
export function getMobxDependencies(includeStateTree = false) {
  const deps = {
    mobx: mobx.mobx,
    "mobx-react-lite": mobx.mobxReactLite
  }
  
  if (includeStateTree) {
    deps["mobx-state-tree"] = mobx.mobxStateTree
  }
  
  return deps
}

Wire Dependency Resolution

File: src/builders/dependency-resolver.js

Update state management dependency resolution:

getStateManagementDependencies(stateChoice, framework) {
  switch (stateChoice) {
    case 'redux':
      return getReduxDependencies()
    case 'zustand':
      return getZustandDependencies()
    case 'mobx':
      return getMobxDependencies()
    case 'jotai':
      return getJotaiDependencies()
    case 'recoil':
      return getRecoilDependencies()
    case 'valtio':
      return getValtioDependencies()
    case 'none':
    default:
      return {}
  }
}
 
// Handle framework-specific considerations
handleStateFrameworkIntegration(stateChoice, framework) {
  const integrations = {}
 
  if (framework === 'nextjs') {
    switch (stateChoice) {
      case 'mobx':
        // MobX needs SSR configuration
        integrations.ssrConfig = {
          enableStaticRendering: true
        }
        break
      case 'recoil':
        // Recoil experimental SSR support
        integrations.experimental = true
        break
    }
  }
 
  return integrations
}

Create State Setup Module

File: src/features/state-management/your-state-setup.js

Create comprehensive setup modules:

// src/features/state-management/mobx-setup.js
import { CORE_UTILS } from '../../utils/index.js'
import path from 'path'
 
export class MobxSetup {
  async setup(projectPath, userChoices) {
    await this.createStores(projectPath, userChoices)
    await this.createRootStore(projectPath, userChoices)
    await this.createProvider(projectPath, userChoices)
    await this.createExampleComponent(projectPath, userChoices)
    await this.handleSSR(projectPath, userChoices)
  }
  
  async createStores(projectPath, userChoices) {
    const { typescript } = userChoices
    
    // Counter Store
    const counterStoreContent = `
import { makeAutoObservable } from 'mobx'
 
${typescript ? `
export interface CounterState {
  count: number
}
` : ''}
 
class CounterStore${typescript ? ' implements CounterState' : ''} {
  count = 0
  
  constructor() {
    makeAutoObservable(this)
  }
  
  increment = () => {
    this.count += 1
  }
  
  decrement = () => {
    this.count -= 1
  }
  
  reset = () => {
    this.count = 0
  }
  
  incrementBy = (amount${typescript ? ': number' : ''}) => {
    this.count += amount
  }
  
  // Computed values
  get isPositive() {
    return this.count > 0
  }
  
  get isNegative() {
    return this.count < 0
  }
  
  get doubleCount() {
    return this.count * 2
  }
}
 
export default CounterStore
    `
    
    const counterPath = path.join(projectPath, 'src', 'stores', `CounterStore.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.ensureDirectory(path.dirname(counterPath))
    await CORE_UTILS.writeFile(counterPath, counterStoreContent.trim())
    
    // User Store
    const userStoreContent = `
import { makeAutoObservable, runInAction } from 'mobx'
 
${typescript ? `
export interface User {
  id: number
  name: string
  email: string
}
 
export interface UserState {
  users: User[]
  currentUser: User | null
  loading: boolean
  error: string | null
}
` : ''}
 
class UserStore${typescript ? ' implements UserState' : ''} {
  users${typescript ? ': User[]' : ''} = []
  currentUser${typescript ? ': User | null' : ''} = null
  loading = false
  error${typescript ? ': string | null' : ''} = null
  
  constructor() {
    makeAutoObservable(this)
  }
  
  // Actions
  setLoading = (loading${typescript ? ': boolean' : ''}) => {
    this.loading = loading
  }
  
  setError = (error${typescript ? ': string | null' : ''}) => {
    this.error = error
  }
  
  setUsers = (users${typescript ? ': User[]' : ''}) => {
    this.users = users
  }
  
  addUser = (user${typescript ? ': User' : ''}) => {
    this.users.push(user)
  }
  
  removeUser = (id${typescript ? ': number' : ''}) => {
    this.users = this.users.filter(user => user.id !== id)
  }
  
  setCurrentUser = (user${typescript ? ': User | null' : ''}) => {
    this.currentUser = user
  }
  
  // Async actions
  fetchUsers = async () => {
    this.setLoading(true)
    this.setError(null)
    
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000))
      const users = [
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
      ]
      
      runInAction(() => {
        this.setUsers(users)
        this.setLoading(false)
      })
    } catch (error) {
      runInAction(() => {
        this.setError(error.message || 'Failed to fetch users')
        this.setLoading(false)
      })
    }
  }
  
  // Computed values
  get userCount() {
    return this.users.length
  }
  
  get hasUsers() {
    return this.users.length > 0
  }
}
 
export default UserStore
    `
    
    const userPath = path.join(projectPath, 'src', 'stores', `UserStore.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.writeFile(userPath, userStoreContent.trim())
  }
  
  async createRootStore(projectPath, userChoices) {
    const { typescript } = userChoices
    
    const rootStoreContent = `
import CounterStore from './CounterStore'
import UserStore from './UserStore'
 
${typescript ? `
export interface RootStoreType {
  counterStore: CounterStore
  userStore: UserStore
}
` : ''}
 
class RootStore${typescript ? ' implements RootStoreType' : ''} {
  counterStore${typescript ? ': CounterStore' : ''}
  userStore${typescript ? ': UserStore' : ''}
  
  constructor() {
    this.counterStore = new CounterStore()
    this.userStore = new UserStore()
  }
}
 
export default RootStore
    `
    
    const rootPath = path.join(projectPath, 'src', 'stores', `RootStore.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.writeFile(rootPath, rootStoreContent.trim())
  }
  
  async createProvider(projectPath, userChoices) {
    const { typescript } = userChoices
    
    const providerContent = `
import React, { createContext, useContext } from 'react'
import RootStore from '../stores/RootStore'
 
${typescript ? `
interface StoreProviderProps {
  children: React.ReactNode
}
` : ''}
 
const StoreContext = createContext${typescript ? '<RootStore | null>' : ''}(null)
 
export const StoreProvider${typescript ? ': React.FC<StoreProviderProps>' : ''} = ({ children }) => {
  const rootStore = React.useMemo(() => new RootStore(), [])
  
  return (
    <StoreContext.Provider value={rootStore}>
      {children}
    </StoreContext.Provider>
  )
}
 
export const useStore = () => {
  const store = useContext(StoreContext)
  if (!store) {
    throw new Error('useStore must be used within StoreProvider')
  }
  return store
}
 
// Individual store hooks for convenience
export const useCounterStore = () => useStore().counterStore
export const useUserStore = () => useStore().userStore
    `
    
    const providerPath = path.join(projectPath, 'src', 'contexts', `StoreProvider.${typescript ? 'tsx' : 'jsx'}`)
    await CORE_UTILS.ensureDirectory(path.dirname(providerPath))
    await CORE_UTILS.writeFile(providerPath, providerContent.trim())
  }
  
  async handleSSR(projectPath, userChoices) {
    if (userChoices.framework === 'nextjs') {
      // Create SSR configuration for MobX
      const ssrConfigContent = `
import { enableStaticRendering } from 'mobx-react-lite'
 
// Enable static rendering for SSR
enableStaticRendering(typeof window === 'undefined')
      `
      
      const configPath = path.join(projectPath, 'src', 'lib', 'mobx-ssr.ts')
      await CORE_UTILS.ensureDirectory(path.dirname(configPath))
      await CORE_UTILS.writeFile(configPath, ssrConfigContent.trim())
    }
  }
}

Update Prompt System

File: src/prompts/steps/state-management-step.js

Add your state management option:

getChoices(previousAnswers) {
  return [
    {
      name: 'Redux Toolkit (Powerful state management with DevTools)',
      value: 'redux',
      short: 'Redux'
    },
    {
      name: 'Zustand (Lightweight state management)',
      value: 'zustand',
      short: 'Zustand'
    },
    {
      name: 'MobX (Reactive state management)',
      value: 'mobx',
      short: 'MobX'
    },
    {
      name: 'Jotai (Primitive and flexible state management)',
      value: 'jotai',
      short: 'Jotai'
    },
    {
      name: 'Recoil (Experimental state management by Facebook)',
      value: 'recoil',
      short: 'Recoil'
    },
    {
      name: 'Valtio (Proxy-based state management)',
      value: 'valtio',
      short: 'Valtio'
    },
    {
      name: 'None (Use React\'s built-in state)',
      value: 'none',
      short: 'None'
    }
  ]
}
 
getRecommendation(previousAnswers) {
  // Provide intelligent recommendations
  if (previousAnswers.framework === 'nextjs') {
    return {
      choice: 'zustand',
      reason: 'Zustand works excellently with Next.js SSR'
    }
  }
 
  if (previousAnswers.typescript) {
    return {
      choice: 'jotai',
      reason: 'Jotai has excellent TypeScript support and type inference'
    }
  }
 
  return {
    choice: 'zustand',
    reason: 'Lightweight and easy to use for most applications'
  }
}

Integrate with Feature System

File: src/features/state-management/index.js

Update the main state management integration:

import { ReduxSetup } from "./redux-setup.js";
import { ZustandSetup } from "./zustand-setup.js";
import { MobxSetup } from "./mobx-setup.js";
import { JotaiSetup } from "./jotai-setup.js";
import { RecoilSetup } from "./recoil-setup.js";
import { ValtioSetup } from "./valtio-setup.js";
 
export async function setupStateManagement(projectPath, userChoices) {
  const { state } = userChoices;
 
  switch (state) {
    case "redux":
      const reduxSetup = new ReduxSetup();
      await reduxSetup.setup(projectPath, userChoices);
      break;
 
    case "zustand":
      const zustandSetup = new ZustandSetup();
      await zustandSetup.setup(projectPath, userChoices);
      break;
 
    case "mobx":
      const mobxSetup = new MobxSetup();
      await mobxSetup.setup(projectPath, userChoices);
      break;
 
    case "jotai":
      const jotaiSetup = new JotaiSetup();
      await jotaiSetup.setup(projectPath, userChoices);
      break;
 
    case "recoil":
      const recoilSetup = new RecoilSetup();
      await recoilSetup.setup(projectPath, userChoices);
      break;
 
    case "valtio":
      const valtioSetup = new ValtioSetup();
      await valtioSetup.setup(projectPath, userChoices);
      break;
 
    case "none":
    default:
      // No state management setup
      break;
  }
}
 
export function getStateManagementInfo(userChoices) {
  const { state } = userChoices;
 
  const stateInfo = {
    mobx: {
      description: "Reactive state management through observables",
      features: [
        "Reactive updates",
        "Computed values",
        "Actions",
        "DevTools support",
      ],
      bundleSize: "~16kb",
      learningCurve: "Medium",
    },
    jotai: {
      description: "Primitive and flexible state management",
      features: [
        "Atomic approach",
        "Bottom-up",
        "TypeScript first",
        "Suspense support",
      ],
      bundleSize: "~3kb",
      learningCurve: "Low",
    },
    recoil: {
      description: "Experimental state management by Facebook",
      features: [
        "Atomic state",
        "Derived state",
        "Async queries",
        "Time travel",
      ],
      bundleSize: "~21kb",
      learningCurve: "Medium",
    },
    valtio: {
      description: "Proxy-based state management",
      features: [
        "Proxy-based",
        "Mutable updates",
        "Automatic subscriptions",
        "Vanilla JS",
      ],
      bundleSize: "~3kb",
      learningCurve: "Low",
    },
  };
 
  return stateInfo[state] || null;
}

Update Template System

File: src/templates/frameworks/vite/content-generator.js

Update templates to integrate state management examples:

generateAppComponent(userChoices) {
  const { state, typescript } = userChoices
 
  let imports = [`import React from 'react'`]
  let providerWrapper = null
  let exampleComponent = 'Counter'
 
  switch (state) {
    case 'mobx':
      imports.push(`import { StoreProvider } from './contexts/StoreProvider'`)
      imports.push(`import { Counter } from './components/Counter'`)
      providerWrapper = 'StoreProvider'
      break
 
    case 'jotai':
      imports.push(`import { JotaiProvider } from './providers/JotaiProvider'`)
      imports.push(`import { Counter } from './components/Counter'`)
      providerWrapper = 'JotaiProvider'
      break
 
    case 'recoil':
      imports.push(`import { RecoilRoot } from 'recoil'`)
      imports.push(`import { Counter } from './components/Counter'`)
      providerWrapper = 'RecoilRoot'
      break
 
    case 'valtio':
      imports.push(`import { Counter } from './components/Counter'`)
      // Valtio doesn't need a provider
      break
  }
 
  return `${imports.join('\n')}
 
function App() {
  return (
    ${providerWrapper ? `<${providerWrapper}>` : ''}
    <div className="min-h-screen bg-gray-100 flex items-center justify-center">
      <div className="text-center">
        <h1 className="text-4xl font-bold text-gray-900 mb-4">
          Welcome to React Kickstart!
        </h1>
        <p className="text-lg text-gray-600 mb-8">
          Your React app with ${state} is ready to go.
        </p>
        ${state !== 'none' ? `<${exampleComponent} />` : ''}
      </div>
    </div>
    ${providerWrapper ? `</${providerWrapper}>` : ''}
  )
}
 
export default App`
}

Add to QA System

File: qa-automation/test-matrix-generator.js

Add your state management options to the test matrix:

const CONFIG_OPTIONS = {
  framework: ["vite", "nextjs"],
  typescript: [true, false],
  state: ["redux", "zustand", "mobx", "jotai", "recoil", "valtio", "none"],
  // ... other options
};
 
// Add state-specific validation
const STATE_VALIDATION = {
  mobx: {
    requiredFiles: [
      "src/stores/RootStore.ts",
      "src/contexts/StoreProvider.tsx",
    ],
    requiredDependencies: ["mobx", "mobx-react-lite"],
  },
  jotai: {
    requiredFiles: ["src/atoms/index.ts", "src/providers/JotaiProvider.tsx"],
    requiredDependencies: ["jotai"],
  },
  recoil: {
    requiredFiles: ["src/recoil/atoms.ts", "src/recoil/selectors.ts"],
    requiredDependencies: ["recoil"],
  },
  valtio: {
    requiredFiles: ["src/stores/store.ts"],
    requiredDependencies: ["valtio"],
  },
};

๐Ÿงช Testing Your Integration

Manual Testing

Test your state management option:

# Test with Vite + TypeScript
node bin/react-kickstart.js test-mobx --yes --framework vite --state mobx --typescript
 
# Test with Next.js
node bin/react-kickstart.js test-jotai --yes --framework nextjs --state jotai
 
# Test with other features
node bin/react-kickstart.js test-recoil --yes --framework vite --state recoil --api axios --styling tailwind

Integration Testing

Verify the generated project works:

cd test-mobx
npm install
npm run dev  # Should start without errors
npm run build  # Should build successfully

QA Automation

Run comprehensive tests:

# Generate updated test matrix
node qa-automation/test-matrix-generator.js
 
# Run critical tests
node qa-automation/test-runner.js critical --full

๐Ÿ“š Best Practices

State Management Design

Consistent Patterns: Follow consistent patterns for state structure, actions, and component integration across all state management solutions.

  • Store Structure - Organize state logically with clear separation of concerns
  • Action Patterns - Use consistent action naming and structure
  • Type Safety - Provide full TypeScript support with proper type definitions
  • DevTools Integration - Include development tools and debugging support
  • Performance - Consider performance implications and optimization strategies

Framework Compatibility

  • SSR Support - Handle server-side rendering requirements for Next.js
  • Hydration - Ensure proper hydration for SSR applications
  • Bundle Size - Consider the impact on bundle size
  • Tree Shaking - Ensure proper tree shaking support

Developer Experience

  • Documentation - Provide clear usage examples and patterns
  • Error Handling - Include proper error boundaries and error handling
  • Testing Utilities - Provide utilities for testing state management
  • Migration Guides - Help developers migrate between state solutions

๐Ÿ“š Next Steps

After Integration:

  1. Test Thoroughly - Test with all framework and feature combinations
  2. Performance Testing - Benchmark performance with different state sizes
  3. Documentation - Create comprehensive usage guides and examples
  4. Community Feedback - Gather feedback from developers using the integration

Related Guides:

State Management Ecosystem: Your state management integration expands React Kickstart's capabilities and provides developers with modern, efficient options for managing application state.