Skip to main content

πŸŽ›οΈ What is State Management?

πŸ“– Definition​

State refers to the data and UI state of an application, and state management is the method of efficiently storing, updating, and sharing this state. In React, when multiple components use the same data, state management libraries can reduce complexity and keep code clean.

🎯 Understanding Through Analogies​

Bank Account Analogy​

Comparing state management to a banking system:

Personal Wallet (Component State)
β”œβ”€ Used only by yourself
β”œβ”€ Simple and fast
└─ Cannot share with others

Family Joint Account (Context API)
β”œβ”€ Accessible to all family members
β”œβ”€ Easy to set up
└─ Gets complicated with many family members

Banking System (Redux/Zustand)
β”œβ”€ Centralized management
β”œβ”€ Records all transactions
β”œβ”€ Complex but systematic
└─ Auditable

Example:
User login information
β”œβ”€ Header: Display user name
β”œβ”€ Sidebar: Profile picture
β”œβ”€ Settings: Email information
└─ Footer: Logout button

All components need the same user information
β†’ Manage in one place with state management!

Broadcasting Station Analogy​

Local Broadcasting (Local State)
- Only within one component
- Uses useState
Example: Modal open/close state

Central Broadcasting (Global State)
- Accessible throughout the entire app
- Uses Redux/Zustand
Example: Logged-in user information

Breaking News (State Updates)
- Sent from the center
- Delivered to all subscribers
- Automatic UI updates

Remote Control (Actions)
- Request channel change
- Request volume adjustment
- Clear interface

Library Analogy​

Note on Desk (Local State)
- Personal workspace
- Fast and simple
- Others can't see it

Library Bulletin Board (Context API)
- Shared by users on the same floor
- Medium scale
- Easy to set up

Central Database (Redux)
- Entire library system
- Tracks all book locations
- Manages loan records
- Systematic but complex

Real-time Notifications (Reactive State)
- Automatic notification when book is returned
- Sent only to subscribers
- Recoil/Jotai approach

βš™οΈ How It Works​

1. Props Drilling Problem​

// ❌ Props Drilling (Stairway to Hell)

// App.js
function App() {
const [user, setUser] = useState({ name: 'κΉ€μ² μˆ˜', email: 'kim@example.com' });

return <Layout user={user} setUser={setUser} />;
}

// Layout.js
function Layout({ user, setUser }) {
return (
<div>
<Header user={user} setUser={setUser} />
<Sidebar user={user} />
<Main user={user} setUser={setUser} />
</div>
);
}

// Header.js
function Header({ user, setUser }) {
return <UserMenu user={user} setUser={setUser} />;
}

// UserMenu.js
function UserMenu({ user, setUser }) {
return <ProfileButton user={user} setUser={setUser} />;
}

// ProfileButton.js
function ProfileButton({ user, setUser }) {
// Finally used!
return (
<button onClick={() => setUser({ ...user, name: '이영희' })}>
{user.name}
</button>
);
}

// Problems:
// 1. user passed through 4 levels (App β†’ Layout β†’ Header β†’ UserMenu β†’ ProfileButton)
// 2. Intermediate components unnecessarily receive props
// 3. All components must be modified when adding/modifying props
// 4. Increased code complexity
// 5. Difficult to maintain

2. State Management Solution​

// βœ… Using State Management Library

// store.js (central store)
const store = {
user: { name: 'κΉ€μ² μˆ˜', email: 'kim@example.com' }
};

// App.js
function App() {
return (
<div>
<Header />
<Sidebar />
<Main />
</div>
);
// No need to pass props!
}

// ProfileButton.js
function ProfileButton() {
// Only components that need it access directly
const user = useStore(state => state.user);
const updateUser = useStore(state => state.updateUser);

return (
<button onClick={() => updateUser({ name: '이영희' })}>
{user.name}
</button>
);
}

// Advantages:
// 1. No need to pass props
// 2. No impact on intermediate components
// 3. Concise code
// 4. Easy to maintain
// 5. Performance optimization possible

3. Unidirectional Data Flow​

// Basic principle of state management

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Store β”‚
β”‚ { count: 0, user: {...}, ... } β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ subscribe
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component β”‚
β”‚ UI rendering: <div>{count}</div> β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ event (e.g., button click)
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Action β”‚
β”‚ { type: 'INCREMENT' } β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ dispatch
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Reducer β”‚
β”‚ count = count + 1 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ state update
↓
Back to Store β†°

// Characteristics:
// 1. Predictable: same input β†’ same output
// 2. Traceable: all changes are recorded
// 3. Easy debugging: time travel possible
// 4. Easy testing: pure functions

πŸ’‘ Real Examples​

Context API (Basic)​

// 1. Create Context
import { createContext, useContext, useState } from 'react';

const UserContext = createContext();

// 2. Provider Component
function UserProvider({ children }) {
const [user, setUser] = useState({
name: 'κΉ€μ² μˆ˜',
email: 'kim@example.com',
isLoggedIn: false
});

const login = (email, password) => {
// API call
setUser({
name: 'κΉ€μ² μˆ˜',
email: email,
isLoggedIn: true
});
};

const logout = () => {
setUser({
name: '',
email: '',
isLoggedIn: false
});
};

return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}

// 3. Custom Hook
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}

// 4. Wrap with Provider in App
function App() {
return (
<UserProvider>
<Header />
<Main />
<Footer />
</UserProvider>
);
}

// 5. Usage
function Header() {
const { user, logout } = useUser();

return (
<header>
<h1>μ•ˆλ…•ν•˜μ„Έμš”, {user.name}λ‹˜</h1>
{user.isLoggedIn && (
<button onClick={logout}>λ‘œκ·Έμ•„μ›ƒ</button>
)}
</header>
);
}

function LoginForm() {
const { login } = useUser();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">둜그인</button>
</form>
);
}

// Pros and Cons of Context API:
// Pros:
// - No additional library needed
// - Simple setup
// - Built-in React feature

// Cons:
// - Difficult to optimize performance
// - Provider hell (nested providers)
// - No DevTools

Redux (Complex but Powerful)​

// 1. Store Setup
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

// Create Slice (state + reducer)
const userSlice = createSlice({
name: 'user',
initialState: {
name: '',
email: '',
isLoggedIn: false,
loading: false,
error: null
},
reducers: {
loginStart: (state) => {
state.loading = true;
state.error = null;
},
loginSuccess: (state, action) => {
state.name = action.payload.name;
state.email = action.payload.email;
state.isLoggedIn = true;
state.loading = false;
},
loginFailure: (state, action) => {
state.loading = false;
state.error = action.payload;
},
logout: (state) => {
state.name = '';
state.email = '';
state.isLoggedIn = false;
}
}
});

// Actions export
export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions;

// Thunk (async action)
export const loginAsync = (email, password) => async (dispatch) => {
dispatch(loginStart());
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
dispatch(loginSuccess(data));
} catch (error) {
dispatch(loginFailure(error.message));
}
};

// Create Store
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});

export default store;

// 2. Connect Provider to App
// index.js
import { Provider } from 'react-redux';
import store from './store';

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

// 3. Use in Component
// LoginForm.js
import { useDispatch, useSelector } from 'react-redux';
import { loginAsync } from './store';

function LoginForm() {
const dispatch = useDispatch();
const { loading, error } = useSelector(state => state.user);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginAsync(email, password));
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? '둜그인 쀑...' : '둜그인'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</form>
);
}

// Header.js
import { useDispatch, useSelector } from 'react-redux';
import { logout } from './store';

function Header() {
const dispatch = useDispatch();
const { name, isLoggedIn } = useSelector(state => state.user);

return (
<header>
{isLoggedIn && (
<>
<span>μ•ˆλ…•ν•˜μ„Έμš”, {name}λ‹˜</span>
<button onClick={() => dispatch(logout())}>
λ‘œκ·Έμ•„μ›ƒ
</button>
</>
)}
</header>
);
}

// Pros and Cons of Redux:
// Pros:
// - Powerful DevTools (time travel debugging)
// - Middleware (logging, async handling)
// - Huge ecosystem
// - Predictable state management
// - Strict structure

// Cons:
// - Lots of boilerplate
// - Steep learning curve
// - Overkill for simple apps

Zustand (Simple and Modern)​

// 1. Create Store
// store.js
import create from 'zustand';

const useStore = create((set, get) => ({
// State
user: {
name: '',
email: '',
isLoggedIn: false
},
loading: false,
error: null,

// Actions
login: async (email, password) => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
set({
user: {
name: data.name,
email: data.email,
isLoggedIn: true
},
loading: false
});
} catch (error) {
set({ error: error.message, loading: false });
}
},

logout: () => {
set({
user: {
name: '',
email: '',
isLoggedIn: false
}
});
},

// Derived state (computed)
getFullName: () => {
const { user } = get();
return user.name || '게슀트';
}
}));

export default useStore;

// 2. Use in Component (No Provider needed!)
// LoginForm.js
import useStore from './store';

function LoginForm() {
const { login, loading, error } = useStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button disabled={loading}>
{loading ? '둜그인 쀑...' : '둜그인'}
</button>
{error && <p>{error}</p>}
</form>
);
}

// Header.js
import useStore from './store';

function Header() {
// Select only the state you need (performance optimization)
const user = useStore(state => state.user);
const logout = useStore(state => state.logout);

return (
<header>
{user.isLoggedIn && (
<>
<span>μ•ˆλ…•ν•˜μ„Έμš”, {user.name}λ‹˜</span>
<button onClick={logout}>λ‘œκ·Έμ•„μ›ƒ</button>
</>
)}
</header>
);
}

// 3. Using Middleware
import create from 'zustand';
import { persist } from 'zustand/middleware';

// Automatically save to localStorage
const useStore = create(
persist(
(set) => ({
user: { name: '', email: '', isLoggedIn: false },
login: async (email, password) => {
// ...
}
}),
{
name: 'user-storage' // localStorage key
}
)
);

// Pros and Cons of Zustand:
// Pros:
// - Very simple API
// - No Provider needed
// - Perfect TypeScript support
// - Small bundle size (1KB)
// - Redux DevTools support
// - Middleware support

// Cons:
// - Small ecosystem
// - Complex apps?
// - Lack of official documentation

Recoil (React-Friendly)​

// 1. Atoms (state units)
// atoms.js
import { atom, selector } from 'recoil';

// Atom: piece of state
export const userState = atom({
key: 'userState',
default: {
name: '',
email: '',
isLoggedIn: false
}
});

export const loadingState = atom({
key: 'loadingState',
default: false
});

// Selector: derived state
export const userNameState = selector({
key: 'userNameState',
get: ({ get }) => {
const user = get(userState);
return user.name || '게슀트';
}
});

// Async Selector
export const userDataQuery = selector({
key: 'userDataQuery',
get: async ({ get }) => {
const response = await fetch('/api/user');
return response.json();
}
});

// 2. Add RecoilRoot to App
// App.js
import { RecoilRoot } from 'recoil';

function App() {
return (
<RecoilRoot>
<Header />
<Main />
<Footer />
</RecoilRoot>
);
}

// 3. Use in Component
// LoginForm.js
import { useRecoilState, useSetRecoilState } from 'recoil';
import { userState, loadingState } from './atoms';

function LoginForm() {
const [user, setUser] = useRecoilState(userState);
const setLoading = useSetRecoilState(loadingState);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleLogin = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
setUser({
name: data.name,
email: data.email,
isLoggedIn: true
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};

return (
<form onSubmit={handleLogin}>
{/* ... */}
</form>
);
}

// Header.js
import { useRecoilValue } from 'recoil';
import { userNameState } from './atoms';

function Header() {
const userName = useRecoilValue(userNameState);

return (
<header>
<span>μ•ˆλ…•ν•˜μ„Έμš”, {userName}λ‹˜</span>
</header>
);
}

// 4. Async Data
import { useRecoilValueLoadable } from 'recoil';
import { userDataQuery } from './atoms';

function UserProfile() {
const userLoadable = useRecoilValueLoadable(userDataQuery);

switch (userLoadable.state) {
case 'loading':
return <div>λ‘œλ”© 쀑...</div>;
case 'hasError':
return <div>μ—λŸ¬: {userLoadable.contents.message}</div>;
case 'hasValue':
return <div>{userLoadable.contents.name}</div>;
}
}

// Pros and Cons of Recoil:
// Pros:
// - React Hooks style
// - Easy async handling
// - Automatic derived state calculation
// - Concurrent Mode support
// - Made by Facebook

// Cons:
// - Still experimental
// - Stability concerns
// - Small ecosystem
// - Lack of DevTools

Comparison Example: Shopping Cart​

// Context API version
const CartContext = createContext();

function CartProvider({ children }) {
const [items, setItems] = useState([]);

const addItem = (item) => {
setItems([...items, item]);
};

const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};

const total = items.reduce((sum, item) => sum + item.price, 0);

return (
<CartContext.Provider value={{ items, addItem, removeItem, total }}>
{children}
</CartContext.Provider>
);
}

// Redux version
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
addItem: (state, action) => {
state.items.push(action.payload);
},
removeItem: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
}
}
});

// Zustand version
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item]
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id)
})),
total: () => {
const { items } = get();
return items.reduce((sum, item) => sum + item.price, 0);
}
}));

// Recoil version
const cartState = atom({
key: 'cartState',
default: []
});

const cartTotalSelector = selector({
key: 'cartTotalSelector',
get: ({ get }) => {
const cart = get(cartState);
return cart.reduce((sum, item) => sum + item.price, 0);
}
});

// Usage
function ShoppingCart() {
// Context
const { items, removeItem, total } = useContext(CartContext);

// Redux
const items = useSelector(state => state.cart.items);
const dispatch = useDispatch();

// Zustand
const { items, removeItem, total } = useCartStore();

// Recoil
const [items, setItems] = useRecoilState(cartState);
const total = useRecoilValue(cartTotalSelector);

return (
<div>
{items.map(item => (
<div key={item.id}>
{item.name} - {item.price}원
<button onClick={() => removeItem(item.id)}>μ‚­μ œ</button>
</div>
))}
<p>총합: {total}원</p>
</div>
);
}

Performance Optimization​

// 1. Context API Performance Issue
function BadContext() {
const [user, setUser] = useState({ name: '', age: 0 });
const [theme, setTheme] = useState('light');

// Problem: even if only theme changes, all components using user re-render
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</AppContext.Provider>
);
}

// βœ… Solution: Separate Contexts
function GoodContext() {
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}

// 2. Redux Selector Optimization
// ❌ Bad Example
function BadComponent() {
const state = useSelector(state => state); // entire state!
return <div>{state.user.name}</div>;
}

// βœ… Good Example
function GoodComponent() {
const userName = useSelector(state => state.user.name); // only what's needed
return <div>{userName}</div>;
}

// 3. Zustand Selector Optimization
// ❌ Bad Example
function BadComponent() {
const store = useStore(); // entire store
return <div>{store.user.name}</div>;
}

// βœ… Good Example
function GoodComponent() {
const userName = useStore(state => state.user.name); // only what's needed
return <div>{userName}</div>;
}

// 4. Recoil atomFamily (dynamic state)
const userItemState = atomFamily({
key: 'UserItem',
default: id => fetch(`/api/users/${id}`).then(r => r.json())
});

function UserProfile({ userId }) {
const user = useRecoilValue(userItemState(userId));
return <div>{user.name}</div>;
}

// 5. Memoization
import { memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
console.log('λ Œλ”λ§:', value);
return <div>{value}</div>;
});

// No re-render if value is the same!

πŸ€” Frequently Asked Questions​

Q1. Which state management library should I choose?​

A: It depends on project scale and requirements:

// Selection Guide

// Context API - Simple apps
const contextUseCases = {
suitableFor: [
'Small projects',
'Minimal state sharing',
'Don\'t want additional libraries',
'Learning purposes'
],
examples: [
'Theme (dark/light mode)',
'Language settings',
'Simple authentication state',
'Modal state'
],
pros: 'No separate installation required',
cons: 'Difficult to optimize performance'
};

// Redux - Large, complex apps
const reduxUseCases = {
suitableFor: [
'Large projects',
'Complex state logic',
'Need time travel debugging',
'Want strict structure',
'Team projects'
],
examples: [
'Enterprise dashboards',
'Complex e-commerce',
'Financial applications',
'SaaS platforms'
],
pros: 'Powerful DevTools, ecosystem',
cons: 'Learning curve, boilerplate'
};

// Zustand - Medium-sized, modern
const zustandUseCases = {
suitableFor: [
'Medium-sized projects',
'Prefer simple API',
'Fast development',
'TypeScript usage'
],
examples: [
'General web apps',
'MVP development',
'Startup projects',
'Prototypes'
],
pros: 'Simplicity, small bundle',
cons: 'Small ecosystem'
};

// Recoil - React-centric, async
const recoilUseCases = {
suitableFor: [
'Prefer React style',
'Lots of async data',
'Complex derived state',
'Experimental projects'
],
examples: [
'Real-time collaboration tools',
'Data visualization',
'Complex forms',
'GraphQL apps'
],
pros: 'React-like, easy async',
cons: 'Still experimental'
};

// Jotai - Recoil alternative
const jotaiUseCases = {
suitableFor: [
'Want Recoil but need stability',
'Atomic state units',
'Small bundle size'
],
pros: 'Simple, stable',
cons: 'Small ecosystem'
};

// Decision tree
function chooseStateManagement(project) {
if (project.size === 'small') {
return 'Context API';
}

if (project.size === 'large' && project.needsStructure) {
return 'Redux Toolkit';
}

if (project.needsSimplicity && project.size === 'medium') {
return 'Zustand';
}

if (project.hasAsyncData && project.likesReactStyle) {
return 'Recoil or Jotai';
}

return 'Zustand'; // works well for most cases
}

Q2. Should I manage all state globally?​

A: No, only manage what's necessary globally:

// State Classification

// 1. Local State (within component)
function SearchBox() {
const [query, setQuery] = useState(''); // local state
const [isFocused, setIsFocused] = useState(false);

// State that other components don't need
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
);
}

// 2. Lifted State (lift to parent)
function Parent() {
const [count, setCount] = useState(0); // shared by multiple children

return (
<div>
<ChildA count={count} />
<ChildB setCount={setCount} />
</div>
);
}

// 3. Global State
// - Accessed by many components
// - Affects entire app
const globalStates = {
user: 'Logged-in user information',
theme: 'Dark/light mode',
language: 'Language settings',
cart: 'Shopping cart',
notifications: 'Notification list'
};

// 4. Server State (server data)
// Recommended: React Query, SWR
function UserProfile() {
const { data, error, isLoading } = useQuery('user', fetchUser);

if (isLoading) return <div>λ‘œλ”© 쀑...</div>;
if (error) return <div>μ—λŸ¬!</div>;
return <div>{data.name}</div>;
}

// Principles:
// - Default: Local State
// - Shared by 2-3 components β†’ Lifted State
// - Shared by many components β†’ Global State
// - API data β†’ Server State (React Query/SWR)

Q3. What's the difference between Redux Toolkit and Redux?​

A: Redux Toolkit is the official tool that makes Redux easier to use:

// Traditional Redux (complex)
// actionTypes.js
const INCREMENT = 'counter/increment';
const DECREMENT = 'counter/decrement';

// actions.js
function increment() {
return { type: INCREMENT };
}

function decrement() {
return { type: DECREMENT };
}

// reducer.js
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { ...state, value: state.value + 1 };
case DECREMENT:
return { ...state, value: state.value - 1 };
default:
return state;
}
}

// store.js
import { createStore } from 'redux';
const store = createStore(counterReducer);

// Redux Toolkit (simple)
// counterSlice.js
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // Immutability handled automatically with Immer!
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});

// Advantages of Redux Toolkit:
// 1. Significantly reduced boilerplate
// 2. Automatic immutability handling with Immer
// 3. Redux DevTools auto-configured
// 4. Easy async with createAsyncThunk
// 5. Excellent TypeScript support

// Async handling comparison
// Traditional Redux (redux-thunk)
function fetchUser(id) {
return async (dispatch) => {
dispatch({ type: 'user/fetchStart' });
try {
const user = await api.fetchUser(id);
dispatch({ type: 'user/fetchSuccess', payload: user });
} catch (error) {
dispatch({ type: 'user/fetchFailure', payload: error });
}
};
}

// Redux Toolkit (createAsyncThunk)
const fetchUser = createAsyncThunk(
'user/fetch',
async (id) => {
const user = await api.fetchUser(id);
return user;
}
);

const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});

// Conclusion: Use Redux Toolkit for new projects!

Q4. What's the difference between React Query/SWR and state management libraries?​

A: They serve different purposes:

// State management libraries (Redux, Zustand, etc.)
// Purpose: Client state management
const clientState = {
user: { name: 'κΉ€μ² μˆ˜', isLoggedIn: true }, // Login state
theme: 'dark', // UI settings
cart: [{ id: 1, name: 'μƒν’ˆ' }], // Shopping cart
modal: { isOpen: false } // UI state
};

// React Query/SWR
// Purpose: Server state management (caching, synchronization, revalidation)
import { useQuery, useMutation } from 'react-query';

function UserProfile() {
// Automatic caching, revalidation, loading state management
const { data, isLoading, error } = useQuery('user', fetchUser);

const mutation = useMutation(updateUser, {
onSuccess: () => {
// Cache invalidation
queryClient.invalidateQueries('user');
}
});

return (
<div>
{isLoading && <div>λ‘œλ”© 쀑...</div>}
{error && <div>μ—λŸ¬: {error.message}</div>}
{data && <div>{data.name}</div>}
<button onClick={() => mutation.mutate({ name: 'μƒˆμ΄λ¦„' })}>
μˆ˜μ •
</button>
</div>
);
}

// Using them together (recommended!)
// store.js (Zustand)
const useStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
// Client state only
}));

// UserProfile.js
function UserProfile() {
// Server state: React Query
const { data: user } = useQuery('user', fetchUser);

// Client state: Zustand
const theme = useStore(state => state.theme);

return (
<div className={theme}>
{user.name}
</div>
);
}

// Comparison:
// Redux/Zustand:
// - Client state
// - Manual management
// - Synchronous

// React Query/SWR:
// - Server state
// - Automatic caching/synchronization
// - Asynchronous
// - Background revalidation
// - Optimistic updates

// Conclusion:
// Use both for best results!
// - React Query: API data
// - Zustand: UI state, settings

Q5. Can I develop without state management?​

A: Yes, useState and props are sufficient for simple apps:

// Developing without state management libraries

// 1. Small apps
function SmallApp() {
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);

// Pass with props
return (
<div>
<Header user={user} />
<Counter count={count} setCount={setCount} />
</div>
);
}

// 2. Separate logic with Custom Hooks
function useUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

const login = async (email, password) => {
setLoading(true);
const user = await api.login(email, password);
setUser(user);
setLoading(false);
};

const logout = () => {
setUser(null);
};

return { user, loading, login, logout };
}

function App() {
const { user, login, logout } = useUser();

return (
<div>
<Header user={user} logout={logout} />
<LoginForm login={login} />
</div>
);
}

// 3. Use only React Query
function App() {
const { data: user } = useQuery('user', fetchUser);
const { data: posts } = useQuery('posts', fetchPosts);

return (
<div>
<Header user={user} />
<PostList posts={posts} />
</div>
);
}

// When do you need state management libraries?
const needsStateManagement = {
signs: [
'Props passing through 3+ levels',
'Multiple components use same state',
'Complex state update logic',
'Need global settings (theme, language)',
'Difficult debugging'
]
};

// Conclusion:
// - Start simple (useState + props)
// - When it gets complex, use Context API
// - When it gets more complex, use Zustand/Redux
// - Use React Query/SWR for API data

πŸŽ“ Next Steps​

Once you understand state management, learn these next:

  1. What is React? (document in progress) - Review React basics
  2. What is Virtual DOM? - React rendering principles
  3. Web Performance Optimization (document in progress) - State management performance optimization

🎬 Conclusion​

State management is core to modern web development:

  • Context API: Simple apps, built into React
  • Redux: Large apps, powerful tools
  • Zustand: Medium-sized, simple API
  • Recoil: React style, convenient async

Choose the right tool for your project, and only manage necessary state globally!