Skip to content

Modern State Management in React: Beyond Redux

State management continues to be one of the most discussed topics in the React ecosystem. While Redux has been the go-to solution for many years, modern React development offers numerous alternatives and patterns for managing application state effectively.

The Evolution of State Management

The React state management landscape has evolved significantly:

  1. First Wave: Component State

    • Local state using setState
    • Prop drilling for sharing state
  2. Second Wave: Global State

    • Redux
    • MobX
    • Context API
  3. Modern Wave: Hybrid Approaches

    • Hooks-based solutions
    • Atomic state management
    • Server state solutions

Modern State Management Principles

1. Keep State Close to Where It's Used

jsx
// Instead of global state
const globalStore = {
  userPreferences: {
    theme: 'dark',
    fontSize: 14,
  },
};

// Use local state when possible
function ThemeSettings() {
  const [theme, setTheme] = useState('dark');
  const [fontSize, setFontSize] = useState(14);

  return (
    <div>
      <ThemeSelector value={theme} onChange={setTheme} />
      <FontSizeControl value={fontSize} onChange={setFontSize} />
    </div>
  );
}

2. Separate UI State from Server State

jsx
function UserProfile() {
  // Server state
  const { data: user, isLoading } = useQuery({
    queryKey: ['user'],
    queryFn: fetchUserProfile,
  });

  // UI state
  const [isEditing, setIsEditing] = useState(false);

  if (isLoading) return <Loading />;

  return (
    <div>
      {isEditing ? (
        <UserEditForm user={user} onSave={() => setIsEditing(false)} />
      ) : (
        <UserDisplay user={user} onEdit={() => setIsEditing(true)} />
      )}
    </div>
  );
}

Modern State Management Solutions

1. Zustand: Simple Yet Powerful

Zustand offers a minimalist approach to state management:

jsx
import create from 'zustand';

const useStore = create((set) => ({
  tasks: [],
  addTask: (task) =>
    set((state) => ({
      tasks: [...state.tasks, task],
    })),
  removeTask: (id) =>
    set((state) => ({
      tasks: state.tasks.filter((task) => task.id !== id),
    })),
  clearTasks: () => set({ tasks: [] }),
}));

function TaskList() {
  const tasks = useStore((state) => state.tasks);
  const removeTask = useStore((state) => state.removeTask);

  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>
          {task.title}
          <button onClick={() => removeTask(task.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
}

2. Jotai: Atomic State Management

Jotai's atomic approach allows for granular state management:

jsx
import { atom, useAtom } from 'jotai';

const textAtom = atom('');
const uppercaseAtom = atom((get) => get(textAtom).toUpperCase());

function TextEditor() {
  const [text, setText] = useAtom(textAtom);
  const [uppercase] = useAtom(uppercaseAtom);

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Uppercase: {uppercase}</p>
    </div>
  );
}

3. React Query: Server State Management

Managing server state separately from client state:

jsx
function UserDashboard() {
  const { data: users, isLoading } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  const mutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => {
      queryClient.invalidateQueries(['users']);
    },
  });

  if (isLoading) return <Loading />;

  return (
    <div>
      {users.map((user) => (
        <UserCard
          key={user.id}
          user={user}
          onUpdate={(updates) => mutation.mutate({ id: user.id, ...updates })}
        />
      ))}
    </div>
  );
}

State Management Patterns

1. State Machines for Complex UI

Using XState for managing complex UI states:

jsx
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

const formMachine = createMachine({
  id: 'form',
  initial: 'idle',
  context: {
    data: {},
    error: null,
  },
  states: {
    idle: {
      on: {
        SUBMIT: 'submitting',
      },
    },
    submitting: {
      invoke: {
        src: 'submitForm',
        onDone: 'success',
        onError: 'error',
      },
    },
    success: {
      type: 'final',
    },
    error: {
      on: {
        RETRY: 'submitting',
      },
    },
  },
});

2. Composite State Management

Combining different solutions for different needs:

jsx
function App() {
  // UI State
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  // Form State
  const form = useForm({
    defaultValues: {
      name: '',
      email: '',
    },
  });

  // Server State
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });

  // Global State
  const theme = useStore((state) => state.theme);

  return (
    <ThemeProvider theme={theme}>
      <Layout>
        <Sidebar isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} />
        <MainContent>
          <UserForm form={form} />
          <UserList users={users} />
        </MainContent>
      </Layout>
    </ThemeProvider>
  );
}

Best Practices

  1. State Colocation

    • Keep state as close as possible to where it's used
    • Lift state up only when necessary
    • Use composition to avoid prop drilling
  2. State Splitting

    • Separate UI state from server state
    • Break down complex state into smaller atoms
    • Use appropriate tools for different types of state
  3. Performance Considerations

    • Implement proper memoization
    • Use selective subscriptions
    • Optimize re-renders

Conclusion

Modern state management in React is about choosing the right tool for the right job. While global state management solutions like Redux still have their place, the trend is moving towards more flexible, composable solutions that leverage React's built-in features and modern patterns.

The key is to understand your application's needs and choose the appropriate combination of tools and patterns. Don't be afraid to mix different solutions - use local state for simple UI components, React Query for server state, and a lightweight global state management solution like Zustand or Jotai for shared state.

Additional Resources

Released under the MIT License.