Cheatsheet
Overview

React Theory

Virtual DOM, Fiber, hooks, performance, concurrent mode and more

14 questions

Must Revise Hard

What is the Virtual DOM and how does reconciliation work?

Virtual DOM Reconciliation Diffing React Core
Asked at: Meta Google Amazon Flipkart
๐ŸŒณ

Virtual DOM

The Virtual DOM is a lightweight JavaScript representation of the actual DOM tree. React keeps a copy in memory and uses it to compute minimal real DOM updates.

                            
State/Props Change
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Render Phase    โ”‚  (pure, can be paused/aborted)
โ”‚  Creates new     โ”‚
โ”‚  Virtual DOM     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Diffing         โ”‚  O(n) algorithm with heuristics:
โ”‚  Algorithm       โ”‚  1. Different types โ†’ replace subtree
โ”‚                  โ”‚  2. Same type โ†’ update attributes
โ”‚                  โ”‚  3. Use keys for lists
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Commit Phase    โ”‚  (synchronous, cannot be interrupted)
โ”‚  Apply minimal   โ”‚
โ”‚  DOM mutations   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          

Diffing Heuristics

  • โ€ข Different element types โ†’ destroy old tree, build new tree from scratch
  • โ€ข Same element type โ†’ update only changed attributes
  • โ€ข Children with keys โ†’ O(n) matching instead of O(nยณ) brute force
  • โ€ข Keys must be stable โ€” avoid index as key for dynamic lists
// โŒ Bad: index as key
{items.map((item, index) => (
  <Item key={index} {...item} />
))}
// Problem: inserting at beginning = all keys shift
// React re-renders everything!

// โœ… Good: stable unique id
{items.map(item => (
  <Item key={item.id} {...item} />
))}
Must Revise Hard

What is React Fiber architecture?

Fiber Concurrent Mode Scheduler React Internal
Asked at: Meta Google
๐Ÿงต

React Fiber (React 16+)

Fiber is React's reconciliation engine. It replaces the old recursive reconciler with an incremental, interruptible one using a linked list of 'fiber' nodes.

Why was it needed? Old reconciler was synchronous โ€” a large tree render couldn't be interrupted, causing dropped frames and poor UX.

Feature Old Reconciler (Stack) Fiber
RenderingSynchronous, recursiveIncremental, interruptible
PriorityFIFOPriority-based scheduling
PausingNot possibleCan pause and resume
Concurrent featuresNoYes (useTransition, Suspense)
// Each fiber node represents a unit of work
// Fiber node structure (simplified):
const fiberNode = {
  type: 'div',         // element type
  key: null,
  stateNode: domNode, // actual DOM node

  // Tree structure (linked list โ€” not tree)
  child: firstChildFiber,
  sibling: nextSiblingFiber,
  return: parentFiber,

  // Work
  pendingProps: {},
  memoizedProps: {},
  memoizedState: {},

  // Effect
  effectTag: 'UPDATE',
  nextEffect: null,
};

// React processes one fiber at a time,
// yielding to browser between frames using
// requestIdleCallback / MessageChannel
โšก

Concurrent Features enabled by Fiber

useTransition (mark state updates as non-urgent), useDeferredValue, Suspense for data fetching, automatic batching, Offscreen component.

Must Revise Medium

All React Hooks explained in depth

Hooks useState useEffect useCallback useMemo useRef
Asked at: Meta Google Amazon Flipkart Netflix
Hook Purpose Returns
useStateLocal state[state, setState]
useEffectSide effects after rendercleanup function
useLayoutEffectSide effects before paint (sync)cleanup function
useCallbackMemoize function referencememoized function
useMemoMemoize expensive computationmemoized value
useRefMutable ref / DOM access{ current: value }
useContextRead context valuecontext value
useReducerComplex state management[state, dispatch]
useImperativeHandleCustomize ref exposed to parentvoid
useIdGenerate unique IDsstring id
useTransitionMark state as non-urgent[isPending, startTransition]
useDeferredValueDefer non-urgent re-rendersdeferred value
// useEffect โ€” dependency array patterns
useEffect(() => {
  // runs after every render
});

useEffect(() => {
  // runs only on mount
  return () => cleanup(); // runs on unmount
}, []);

useEffect(() => {
  // runs when a or b changes
  const sub = api.subscribe(a);
  return () => sub.unsubscribe(); // cleanup
}, [a, b]);

// useCallback vs useMemo
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]); // memoizes the function itself

const sortedList = useMemo(() => {
  return list.sort((a, b) => a.name.localeCompare(b.name));
}, [list]); // memoizes the computed value

// useRef โ€” two uses
const inputRef = useRef(null); // DOM ref
useEffect(() => { inputRef.current?.focus(); }, []);

const countRef = useRef(0); // mutable value, no re-render
const handleClick2 = () => { countRef.current++; }; // doesn't trigger re-render

// useReducer โ€” for complex state
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT': return { count: state.count + 1 };
    case 'SET': return { count: action.payload };
    default: return state;
  }
};

const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'INCREMENT' });
dispatch({ type: 'SET', payload: 10 });
Must Revise Hard

React Performance Optimization techniques

Performance memo useMemo useCallback Lazy Loading
Asked at: Meta Google Amazon Netflix Flipkart
  • โ€ข React.memo โ€” skip re-render if props unchanged
  • โ€ข useMemo โ€” memoize expensive computations
  • โ€ข useCallback โ€” stable function references for memo'd children
  • โ€ข Code splitting โ€” React.lazy + Suspense
  • โ€ข Virtualization โ€” react-window for large lists
  • โ€ข Key prop โ€” stable keys for efficient diffing
  • โ€ข Avoid inline objects/functions in JSX
  • โ€ข useTransition โ€” non-urgent state updates
  • โ€ข Avoid anonymous functions in render (they recreate every time)
  • โ€ข Context splitting โ€” separate contexts to minimize re-renders
// React.memo โ€” prevents re-render if props same
const ExpensiveChild = React.memo(({ data, onUpdate }) => {
  return <div>{/* expensive render */}</div>;
}, (prevProps, nextProps) => {
  // Custom comparison: return true to skip re-render
  return prevProps.data.id === nextProps.data.id;
});

// โŒ Problem: new function on every parent render
function Parent() {
  const [count, setCount] = useState(0);
  // This creates a new function every render!
  const handleUpdate = () => updateData();
  return <ExpensiveChild onUpdate={handleUpdate} />;
  // ExpensiveChild re-renders every time even though memo!
}

// โœ… Fix with useCallback
function Parent() {
  const [count, setCount] = useState(0);
  const handleUpdate = useCallback(() => updateData(), []);
  return <ExpensiveChild onUpdate={handleUpdate} />;
}

// Code splitting
const HeavyPage = React.lazy(() => import('./HeavyPage'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyPage />
    </Suspense>
  );
}

// useTransition for filtering large lists
function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    setQuery(e.target.value); // urgent: update input immediately
    startTransition(() => {
      setResults(filterResults(e.target.value)); // non-urgent
    });
  }

  return (
    <>
      <input onChange={handleChange} value={query} />
      {isPending ? <Spinner /> : <ResultList results={results} />}
    </>
  );
}
Must Revise Medium

React Context โ€” proper usage and pitfalls

Context State Management Performance
Asked at: Meta Google Flipkart
โš ๏ธ

Context Performance Issue

Every consumer re-renders when context value changes. Use context for infrequently updated values (theme, locale, user). For frequent updates, use Zustand/Redux or split contexts.

// Creating and using context
const ThemeContext = createContext('light');
const UserContext = createContext(null);

// Provider โ€” wrap once at appropriate level
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);

  // โš ๏ธ Problem: new object on every render!
  // return <UserContext.Provider value={{ user, setUser }}>

  // โœ… Memoize context value
  const userValue = useMemo(() => ({ user, setUser }), [user]);

  return (
    <ThemeContext.Provider value={theme}>
      <UserContext.Provider value={userValue}>
        <Router />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// Consumer โ€” custom hook pattern
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (ctx === undefined) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return ctx;
}

// Context splitting to prevent unnecessary re-renders
// โœ… Split read and write contexts
const CountStateContext = createContext(0);
const CountDispatchContext = createContext(null);

function Counter() {
  const count = useContext(CountStateContext); // only re-renders on count change
  const dispatch = useContext(CountDispatchContext); // never re-renders (dispatch is stable)
  return <button onClick={() => dispatch('INC')}>{count}</button>;
}
Medium

Error Boundaries in React

Error Boundaries Error Handling Class Components
Asked at: Meta Google Amazon
๐Ÿ›ก๏ธ

Error Boundaries

Error boundaries are React class components that catch JavaScript errors in child component tree, log them, and display fallback UI. They do NOT catch: event handlers, async code, SSR, errors in the boundary itself.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    // Update state to show fallback UI
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // Log to error reporting service
    logErrorToService(error, errorInfo.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div>
          <h2>Something went wrong.</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// Usage
<ErrorBoundary fallback={<ErrorPage />}>
  <UserProfile />
</ErrorBoundary>

// react-error-boundary library (hooks-based)
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <p>Error: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
  <Component />
</ErrorBoundary>
Hard

React Suspense and Concurrent Mode

Suspense Concurrent Mode Code Splitting Server Components
Asked at: Meta Google
// Suspense for code splitting
const Profile = lazy(() => import('./Profile'));

<Suspense fallback={<ProfileSkeleton />}>
  <Profile userId={id} />
</Suspense>

// Suspense for data fetching (React 18+)
// With React Query:
function Posts() {
  const { data } = useSuspenseQuery({ queryKey: ['posts'], queryFn: fetchPosts });
  return <PostList posts={data} />; // no loading state needed!
}

<Suspense fallback={<PostsSkeleton />}>
  <Posts />
</Suspense>

// Nested Suspense boundaries
<Suspense fallback={<PageSkeleton />}>
  <Header />
  <Suspense fallback={<FeedSkeleton />}>
    <Feed />
  </Suspense>
  <Suspense fallback={<SidebarSkeleton />}>
    <Sidebar />
  </Suspense>
</Suspense>

// useTransition โ€” mark updates as non-urgent
function TabContainer() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab); // don't block UI during expensive tab switch
    });
  }

  return (
    <>
      <TabButton onClick={() => selectTab('home')}>Home</TabButton>
      <TabButton onClick={() => selectTab('analytics')}>Analytics</TabButton>
      {isPending ? <Spinner /> : <TabPanel tab={tab} />}
    </>
  );
}
Must Revise Medium

Custom Hooks โ€” patterns and best practices

Custom Hooks Patterns Reusability
Asked at: Meta Google Flipkart Amazon
// Custom hooks extract stateful logic

// 1. useFetch โ€” data fetching
function useFetch(url) {
  const [state, setState] = useState({
    data: null, loading: true, error: null
  });

  useEffect(() => {
    let cancelled = false;
    setState({ data: null, loading: true, error: null });

    fetch(url)
      .then(r => r.json())
      .then(data => {
        if (!cancelled) setState({ data, loading: false, error: null });
      })
      .catch(error => {
        if (!cancelled) setState({ data: null, loading: false, error });
      });

    return () => { cancelled = true; }; // cleanup โ€” prevents stale state
  }, [url]);

  return state;
}

// 2. useLocalStorage โ€” persisted state
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setStoredValue = useCallback((newValue) => {
    setValue(newValue);
    window.localStorage.setItem(key, JSON.stringify(newValue));
  }, [key]);

  return [value, setStoredValue];
}

// 3. useDebounce
function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// 4. useIntersectionObserver โ€” infinite scroll / lazy load
function useIntersectionObserver(ref, options = {}) {
  const [isIntersecting, setIntersecting] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => setIntersecting(entry.isIntersecting),
      options
    );
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [ref, options]);

  return isIntersecting;
}
Hard

React Design Patterns โ€” HOC, Render Props, Compound Components

Design Patterns HOC Render Props Compound Components
Asked at: Meta Google Adobe
// 1. Higher-Order Component (HOC)
function withAuth(Component) {
  return function AuthenticatedComponent(props) {
    const { user } = useAuth();
    if (!user) return <Redirect to="/login" />;
    return <Component {...props} user={user} />;
  };
}
const ProtectedDashboard = withAuth(Dashboard);

// 2. Render Props
function Mouse({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
  return <div onMouseMove={handleMove}>{render(position)}</div>;
}

<Mouse render={({ x, y }) => <Cat x={x} y={y} />} />

// 3. Compound Components (most modern)
const Tabs = ({ children, defaultTab }) => {
  const [activeTab, setActiveTab] = useState(defaultTab);
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      {children}
    </TabsContext.Provider>
  );
};

Tabs.List = ({ children }) => <div role="tablist">{children}</div>;
Tabs.Tab = ({ id, children }) => {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      role="tab"
      aria-selected={activeTab === id}
      onClick={() => setActiveTab(id)}>
      {children}
    </button>
  );
};
Tabs.Panel = ({ id, children }) => {
  const { activeTab } = useContext(TabsContext);
  return activeTab === id ? <div role="tabpanel">{children}</div> : null;
};

// Usage โ€” feels like native HTML
<Tabs defaultTab="profile">
  <Tabs.List>
    <Tabs.Tab id="profile">Profile</Tabs.Tab>
    <Tabs.Tab id="settings">Settings</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel id="profile"><ProfileContent /></Tabs.Panel>
  <Tabs.Panel id="settings"><SettingsContent /></Tabs.Panel>
</Tabs>
Hard

React Server Components (RSC) โ€” how they work

Server Components Next.js Performance Streaming
Asked at: Meta Vercel
๐Ÿ–ฅ๏ธ

Key Concept

Server Components run ONLY on the server, never on the client. They can directly access databases/file systems, have zero JS bundle impact, but cannot use state, effects, or browser APIs.

Feature Server Component Client Component
RenderingServer onlyClient (+ optional SSR)
useState/useEffectโŒ Not allowedโœ…
Browser APIsโŒ Not allowedโœ…
Direct DB accessโœ…โŒ
Bundle impact0 bytes JSAdds to bundle
Async/awaitโœ…โŒ (use hooks)
// Server Component (default in Next.js App Router)
// app/users/page.tsx
async function UsersPage() {
  // Direct DB access! No API needed
  const users = await db.query('SELECT * FROM users');

  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// Client Component โ€” 'use client' directive
'use client';
import { useState } from 'react';

function LikeButton({ initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  return (
    <button onClick={() => setLikes(l => l + 1)}>
      โค๏ธ {likes}
    </button>
  );
}

// Mix: pass server data as props to client components
async function PostPage({ id }) {
  const post = await db.posts.findById(id); // server
  return (
    <article>
      <h1>{post.title}</h1>
      <LikeButton initialLikes={post.likes} /> {/* client */}
    </article>
  );
}
Must Revise Easy

Controlled vs Uncontrolled Components in React

Forms Controlled Uncontrolled Refs
Asked at: Amazon Microsoft Flipkart
Feature Controlled Uncontrolled
State managed byReact (useState)DOM itself
Value accessvia state variablevia ref.current.value
Real-time validationโœ… EasyโŒ Harder
Syncing to other stateโœ… EasyโŒ Harder
PerformanceRe-render on each keystrokeNo re-renders during typing
When to useMost cases, complex formsSimple forms, file inputs
// Controlled โ€” React owns the value
function ControlledForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    console.log({ name, email }); // always up to date
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}            // controlled: value from state
        onChange={e => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

// Uncontrolled โ€” DOM owns the value (use refs to access)
function UncontrolledForm() {
  const nameRef = useRef(null);
  const fileRef = useRef(null);

  function handleSubmit(e) {
    e.preventDefault();
    console.log(nameRef.current.value); // read on submit
    console.log(fileRef.current.files[0]); // file input always uncontrolled
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} defaultValue="Alice" placeholder="Name" />
      <input ref={fileRef} type="file" />
      <button type="submit">Submit</button>
    </form>
  );
}
Must Revise Easy

State vs Props โ€” key differences

State Props Fundamentals Data Flow
Asked at: Amazon Flipkart Microsoft
Aspect Props State
Who owns it?Parent componentComponent itself
Mutable?โŒ Read-only (from child's perspective)โœ… Yes (via setState/useState)
Triggers re-render?โœ… Yes (when parent re-renders)โœ… Yes (when state changes)
Passed fromParent to childInitialized internally
PurposeConfigure/customize childTrack component's internal data
function Parent() {
  const [count, setCount] = useState(0); // state โ€” owned by Parent

  return (
    <Child
      count={count}              // prop passed to Child
      onIncrement={() => setCount(c => c + 1)} // prop: callback
    />
  );
}

function Child({ count, onIncrement }) { // receives props
  // โŒ Cannot modify props: count = 5; (error or no effect)
  // โœ… Must call callback to request parent update
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onIncrement}>+1</button>
    </div>
  );
}

// Lifting state up โ€” when siblings need shared state
function App() {
  const [query, setQuery] = useState(''); // lifted to common ancestor

  return (
    <>
      <SearchInput value={query} onChange={setQuery} />
      <SearchResults query={query} />
    </>
  );
}
โฌ‡๏ธ

Unidirectional data flow

Data flows DOWN (parent โ†’ child via props). Events flow UP (child โ†’ parent via callback props). This predictable pattern makes React apps easier to debug.

Must Revise Hard

Stale Closures in React Hooks โ€” the sneaky bug

Stale Closure useEffect useCallback Hooks
Asked at: Meta Google Netflix
๐Ÿ›

The Stale Closure Problem

A hook callback closes over a value at the time it was created. If the value changes but the callback is NOT recreated, it still references the OLD (stale) value.

// โŒ STALE CLOSURE BUG
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // 'count' is captured at 0 (stale!)
      console.log(count); // always logs 0
      setCount(count + 1); // always sets to 1!
    }, 1000);
    return () => clearInterval(interval);
  }, []); // empty deps โ€” callback never updates
}

// โœ… Fix 1: Include dependency
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1); // fresh count each interval
  }, 1000);
  return () => clearInterval(interval);
}, [count]); // re-creates interval when count changes

// โœ… Fix 2: Functional update (best for intervals/timers)
useEffect(() => {
  const interval = setInterval(() => {
    setCount(prev => prev + 1); // always gets fresh prev value
  }, 1000);
  return () => clearInterval(interval);
}, []); // no dependency needed!

// โœ… Fix 3: useRef to always have fresh value
const countRef = useRef(count);
useEffect(() => { countRef.current = count; }, [count]);

useEffect(() => {
  const interval = setInterval(() => {
    console.log(countRef.current); // always fresh!
  }, 1000);
  return () => clearInterval(interval);
}, []);
๐Ÿ’ก

eslint-plugin-react-hooks

The exhaustive-deps ESLint rule catches stale closures by warning when you forget to include dependencies. Always heed its warnings!

Must Revise Hard

Redux โ€” actions, reducers, store, middleware

Redux State Management Middleware Thunk
Asked at: Meta Amazon Flipkart Netflix
                            
  User Action
      โ”‚
      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Action     โ”‚  { type: 'INCREMENT', payload: 1 }
โ”‚   Creator    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚ dispatch(action)
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Middleware  โ”‚โ”€โ”€โ”€โ”€โ–บโ”‚  Async effects  โ”‚
โ”‚  (thunk/     โ”‚     โ”‚  (API calls)    โ”‚
โ”‚   saga)      โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Reducer    โ”‚  (prevState, action) => newState
โ”‚   (pure fn)  โ”‚  never mutates โ€” returns new object
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Store     โ”‚  Single source of truth
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚ triggers
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  React re-   โ”‚  useSelector reads new state
โ”‚  renders     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          
// Modern Redux Toolkit (RTK) โ€” recommended approach
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Slice โ€” actions + reducer in one
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0, status: 'idle' },
  reducers: {
    increment: (state) => { state.value += 1; }, // Immer allows "mutation"
    decrement: (state) => { state.value -= 1; },
    incrementBy: (state, action) => { state.value += action.payload; },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => { state.status = 'loading'; })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'idle';
        state.user = action.payload;
      });
  }
});

// Async thunk โ€” for API calls
const fetchUser = createAsyncThunk('user/fetch', async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

// In component
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <>
      <p>{count}</p>
      <button onClick={() => dispatch(counterSlice.actions.increment())}>+</button>
      <button onClick={() => dispatch(fetchUser(1))}>Fetch User</button>
    </>
  );
}