[{"data":1,"prerenderedAt":396},["ShallowReactive",2],{"topic-react-state-management":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Freact.yml","React interview questions on hooks, components, state and rendering — the most-requested front-end framework in technical interviews.","yml","react",{},"React",2,"frameworks\u002Freact",1,"RA6wemU0xFHrA1ZKCABP1J7MireA3Bt_FCJMOuMgsm0",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Freact-state-management.yml","Redux Toolkit, Zustand, Context vs Redux trade-offs, and async state management with React Query — patterns for managing global and server state in production React apps.",{},"State Management",6,"state-management","topics\u002Freact-state-management","Gq8fJKVnohBmu5ktN9efTFFpmmSJb41he3EQuRxja3w",[25,125,212,299],{"id":26,"title":27,"body":28,"description":32,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":36,"navigation":37,"order":13,"path":38,"questions":39,"questionsCount":118,"related":119,"seo":120,"seoDescription":121,"stem":122,"subtopic":27,"topic":19,"topicSlug":21,"updated":123,"__hash__":124},"qa\u002Freact\u002Fstate-management\u002Fredux-toolkit.md","Redux Toolkit",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":11,"depth":11,"links":33},"",[],"medium","md",{},true,"\u002Freact\u002Fstate-management\u002Fredux-toolkit",[40,45,49,53,57,61,65,69,73,78,82,86,90,94,98,102,106,110,114],{"id":41,"difficulty":42,"q":43,"a":44},"what-problem-rtk-solves","easy","What problems does Redux Toolkit solve compared to plain Redux?","Plain Redux requires a lot of **boilerplate**: action type constants, action\ncreator functions, hand-written immutable updates in reducers, and manual\nmiddleware wiring. Every new feature duplicates this ceremony, leading to\nsprawling files and easy mistakes (e.g., accidentally mutating state).\n\n**Redux Toolkit (RTK)** eliminates this with three design decisions: it\nbundles **Immer** so you write mutating-looking reducer code that is\nactually immutable under the hood; it provides **`createSlice`** which\nauto-generates action creators and action types from a reducer map; and\n**`configureStore`** sets up Redux DevTools and `redux-thunk` middleware\nautomatically.\n\n```js\n\u002F\u002F Plain Redux — three files of ceremony for one counter\nconst INCREMENT = 'counter\u002Fincrement'\nconst increment = () => ({ type: INCREMENT })\nfunction reducer(state = { value: 0 }, action) {\n  switch (action.type) {\n    case INCREMENT: return { ...state, value: state.value + 1 } \u002F\u002F manual spread\n    default: return state\n  }\n}\n\n\u002F\u002F RTK — everything above in ~5 lines\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment(state) { state.value++ } \u002F\u002F Immer handles immutability\n  }\n})\n```\n\n**Rule of thumb:** If you're writing `...state` spreads or a `types.js`\nconstants file, you should be using Redux Toolkit instead.\n",{"id":46,"difficulty":42,"q":47,"a":48},"configure-store-setup","How do you set up a Redux store with configureStore?","**`configureStore`** is RTK's store factory. It accepts a `reducer` map\n(each key becomes a top-level slice of state), and it automatically\nenables the **Redux DevTools Extension** and adds **`redux-thunk`**\nmiddleware — no manual `compose` or `applyMiddleware` needed.\n\n```js\nimport { configureStore } from '@reduxjs\u002Ftoolkit'\nimport counterReducer from '.\u002Ffeatures\u002Fcounter\u002FcounterSlice'\nimport userReducer from '.\u002Ffeatures\u002Fuser\u002FuserSlice'\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterReducer, \u002F\u002F state.counter\n    user: userReducer,       \u002F\u002F state.user\n  },\n  \u002F\u002F middleware and devTools are auto-configured; override only when needed\n})\n\n\u002F\u002F Infer RootState and AppDispatch types (TypeScript)\nexport type RootState = ReturnType\u003Ctypeof store.getState>\nexport type AppDispatch = typeof store.dispatch\n```\n\nWrap your app in `\u003CProvider store={store}>` from `react-redux` so every\ncomponent can access the store via hooks.\n\n**Rule of thumb:** One `configureStore` call per app; export `RootState`\nand `AppDispatch` from the same file so TypeScript consumers stay in sync.\n",{"id":50,"difficulty":42,"q":51,"a":52},"create-slice-basics","What does createSlice do and what does it return?","**`createSlice`** takes a name, an initial state, and a `reducers` object.\nIt returns a **slice object** containing the generated reducer function and\nan `actions` object whose keys match the reducer names.\n\n```js\nimport { createSlice } from '@reduxjs\u002Ftoolkit'\n\nconst counterSlice = createSlice({\n  name: 'counter',            \u002F\u002F prefix for action types: 'counter\u002Fincrement'\n  initialState: { value: 0 },\n  reducers: {\n    increment(state) { state.value++ },           \u002F\u002F action: counterSlice.actions.increment\n    decrement(state) { state.value-- },\n    incrementBy(state, action) {\n      state.value += action.payload               \u002F\u002F payload is the argument passed to the action creator\n    },\n  },\n})\n\nexport const { increment, decrement, incrementBy } = counterSlice.actions\nexport default counterSlice.reducer               \u002F\u002F plug into configureStore\n```\n\nThe action type strings are auto-namespaced as `\"sliceName\u002FreducerName\"`,\nwhich keeps them unique across a large app without a constants file.\n\n**Rule of thumb:** One `createSlice` per feature; export named action\ncreators and the default reducer from the same file.\n",{"id":54,"difficulty":42,"q":55,"a":56},"immer-mutations","How does Immer make Redux reducers easier to write?","RTK's reducers run inside **Immer's `produce`** function. Immer gives you\na **draft proxy** of the current state. Any mutations you apply to the\ndraft are recorded and used to create a new immutable state object —\nyour original state is never changed.\n\n```js\nreducers: {\n  addTodo(state, action) {\n    state.items.push(action.payload)  \u002F\u002F looks like mutation, but Immer produces a new array\n  },\n  removeTodo(state, action) {\n    const index = state.items.findIndex(t => t.id === action.payload)\n    if (index !== -1) state.items.splice(index, 1) \u002F\u002F splice on the draft is safe\n  },\n  updateTodo(state, action) {\n    const todo = state.items.find(t => t.id === action.payload.id)\n    if (todo) todo.text = action.payload.text     \u002F\u002F direct property assignment is fine\n  },\n}\n```\n\nThe one rule: you must either **mutate the draft** OR **return a new\nvalue** — never both in the same reducer case. Returning `undefined`\nimplicitly keeps the current draft.\n\n**Rule of thumb:** Mutate the draft for nested updates; return a\nreplacement value only when you want to replace the entire state slice.\n",{"id":58,"difficulty":42,"q":59,"a":60},"use-selector-use-dispatch","How do useSelector and useDispatch connect components to the Redux store?","**`useSelector`** reads a value from the Redux store. It accepts a\nselector function that receives the full `RootState` and returns the\nvalue you need. The component re-renders only when that value changes.\n**`useDispatch`** returns the store's dispatch function so you can send\nactions.\n\n```jsx\nimport { useSelector, useDispatch } from 'react-redux'\nimport { increment, incrementBy } from '.\u002FcounterSlice'\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value) \u002F\u002F subscribe to one slice\n  const dispatch = useDispatch()\n\n  return (\n    \u003Cdiv>\n      \u003Cp>{count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => dispatch(increment())}>+1\u003C\u002Fbutton>\n      \u003Cbutton onClick={() => dispatch(incrementBy(5))}>+5\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\nIn TypeScript, use typed wrappers (`useAppSelector`, `useAppDispatch`)\nderived from `RootState` and `AppDispatch` so you get full autocomplete\nwithout repeating type annotations in every component.\n\n**Rule of thumb:** Keep selector functions simple in the component; move\ncomplex derived logic to standalone selector functions or `createSelector`.\n",{"id":62,"difficulty":34,"q":63,"a":64},"create-async-thunk","How does createAsyncThunk handle asynchronous actions?","**`createAsyncThunk`** wraps an async function and automatically dispatches\nthree lifecycle actions: `pending`, `fulfilled`, and `rejected`. You handle\nthese in `extraReducers` to update loading\u002Ferror\u002Fdata state.\n\n```js\nimport { createAsyncThunk, createSlice } from '@reduxjs\u002Ftoolkit'\n\n\u002F\u002F First arg: action type prefix; second arg: async payload creator\nexport const fetchUser = createAsyncThunk('user\u002FfetchById', async (userId) => {\n  const res = await fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n  if (!res.ok) throw new Error('Not found')   \u002F\u002F throw to trigger rejected\n  return res.json()                             \u002F\u002F returned value becomes action.payload in fulfilled\n})\n\nconst userSlice = createSlice({\n  name: 'user',\n  initialState: { data: null, status: 'idle', error: null },\n  reducers: {},\n  extraReducers(builder) {\n    builder\n      .addCase(fetchUser.pending,   (state) => { state.status = 'loading' })\n      .addCase(fetchUser.fulfilled, (state, action) => {\n        state.status = 'succeeded'\n        state.data = action.payload             \u002F\u002F Immer draft mutation\n      })\n      .addCase(fetchUser.rejected,  (state, action) => {\n        state.status = 'failed'\n        state.error = action.error.message\n      })\n  },\n})\n```\n\nDispatch it like any action: `dispatch(fetchUser(42))`. The thunk returns\na promise you can `await` and `.unwrap()` to re-throw errors into a\ntry\u002Fcatch in the component.\n\n**Rule of thumb:** Use `createAsyncThunk` for one-off fetches; switch to\nRTK Query when you need caching, deduplication, or automatic refetching.\n",{"id":66,"difficulty":34,"q":67,"a":68},"loading-error-state-pattern","What is the standard pattern for tracking loading and error state with createAsyncThunk?","The convention is to keep a **`status`** field (`'idle' | 'loading' |\n'succeeded' | 'failed'`) alongside an **`error`** field in the slice's\ninitial state. Each lifecycle case updates these fields atomically with\nthe data.\n\n```js\nconst initialState = {\n  items: [],\n  status: 'idle',    \u002F\u002F drives skeleton\u002Fspinner UI\n  error: null,       \u002F\u002F drives error banner UI\n}\n\nextraReducers(builder) {\n  builder\n    .addCase(fetchItems.pending, (state) => {\n      state.status = 'loading'\n      state.error = null              \u002F\u002F clear previous error on retry\n    })\n    .addCase(fetchItems.fulfilled, (state, action) => {\n      state.status = 'succeeded'\n      state.items = action.payload\n    })\n    .addCase(fetchItems.rejected, (state, action) => {\n      state.status = 'failed'\n      state.error = action.error.message ?? 'Unknown error'\n    })\n}\n```\n\nIn the component, read `status` with `useSelector` and branch:\n`if (status === 'loading') return \u003CSpinner \u002F>`. Reset `status` to `'idle'`\nwhen navigating away so the next mount triggers a fresh fetch.\n\n**Rule of thumb:** Never use a boolean `isLoading` flag — a string `status`\nfield handles all four states without impossible combinations.\n",{"id":70,"difficulty":34,"q":71,"a":72},"rtk-query-overview","What is RTK Query and how does it differ from createAsyncThunk?","**RTK Query** is a data-fetching and caching layer built into RTK. It\ngenerates hooks, manages a normalized request cache, handles\ndeduplication, and supports automatic background refetching — things you\nwould have to build manually with `createAsyncThunk`.\n\n```js\nimport { createApi, fetchBaseQuery } from '@reduxjs\u002Ftoolkit\u002Fquery\u002Freact'\n\nexport const postsApi = createApi({\n  reducerPath: 'postsApi',              \u002F\u002F key in the Redux state\n  baseQuery: fetchBaseQuery({ baseUrl: '\u002Fapi' }),\n  endpoints: (builder) => ({\n    getPosts: builder.query({\n      query: () => '\u002Fposts',            \u002F\u002F appended to baseUrl\n    }),\n    getPostById: builder.query({\n      query: (id) => `\u002Fposts\u002F${id}`,\n    }),\n  }),\n})\n\n\u002F\u002F Auto-generated hooks follow the pattern: use\u003CEndpointName>Query\nexport const { useGetPostsQuery, useGetPostByIdQuery } = postsApi\n```\n\nAdd `postsApi.reducer` to `configureStore` and\n`postsApi.middleware` to the middleware chain. The hooks return\n`{ data, isLoading, isError }` — no manual `extraReducers` needed.\n\n**Rule of thumb:** Default to RTK Query for server data; use\n`createAsyncThunk` only for non-idempotent mutations that don't fit a\nREST endpoint pattern.\n",{"id":74,"difficulty":75,"q":76,"a":77},"rtk-query-cache-invalidation","hard","How does RTK Query handle cache invalidation with tags?","RTK Query uses a **tag** system to express which queries are invalidated\nwhen a mutation runs. A query **provides** tags; a mutation **invalidates**\nthose tags, causing every matching query to refetch.\n\n```js\nexport const postsApi = createApi({\n  reducerPath: 'postsApi',\n  baseQuery: fetchBaseQuery({ baseUrl: '\u002Fapi' }),\n  tagTypes: ['Post'],                          \u002F\u002F register tag names\n  endpoints: (builder) => ({\n    getPosts: builder.query({\n      query: () => '\u002Fposts',\n      providesTags: ['Post'],                  \u002F\u002F this cache entry is tagged 'Post'\n    }),\n    getPostById: builder.query({\n      query: (id) => `\u002Fposts\u002F${id}`,\n      providesTags: (result, error, id) => [{ type: 'Post', id }], \u002F\u002F fine-grained tag\n    }),\n    createPost: builder.mutation({\n      query: (body) => ({ url: '\u002Fposts', method: 'POST', body }),\n      invalidatesTags: ['Post'],               \u002F\u002F bust all 'Post' queries on success\n    }),\n    updatePost: builder.mutation({\n      query: ({ id, ...patch }) => ({ url: `\u002Fposts\u002F${id}`, method: 'PATCH', body: patch }),\n      invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }], \u002F\u002F only bust this post\n    }),\n  }),\n})\n```\n\nWhen `updatePost` succeeds, only the `getPostById` query for that specific\n`id` refetches, leaving other cached posts untouched.\n\n**Rule of thumb:** Use list tags (e.g., `'Post'`) to bust the whole\ncollection; use entity tags (`{ type: 'Post', id }`) to bust individual\nitems — mix both in `createPost` to refresh the list and the detail.\n",{"id":79,"difficulty":34,"q":80,"a":81},"rtk-query-hooks-usage","How do you use RTK Query hooks in a component?","The auto-generated **`useGetXQuery`** hooks subscribe the component to the\ncached data. They return a result object with `data`, `isLoading`,\n`isFetching`, `isError`, and `error` properties.\n\n```jsx\nimport { useGetPostsQuery } from '.\u002FpostsApi'\n\nfunction PostsList() {\n  const {\n    data: posts = [],   \u002F\u002F default to [] to avoid null checks on first render\n    isLoading,\n    isError,\n    error,\n    refetch,            \u002F\u002F manually trigger a refetch\n  } = useGetPostsQuery()\n\n  if (isLoading) return \u003Cp>Loading…\u003C\u002Fp>\n  if (isError)   return \u003Cp>Error: {error.message}\u003C\u002Fp>\n\n  return (\n    \u003Cul>\n      {posts.map(post => \u003Cli key={post.id}>{post.title}\u003C\u002Fli>)}\n    \u003C\u002Ful>\n  )\n}\n```\n\nFor mutations, use the auto-generated `useXMutation` hook which returns\na `[trigger, result]` tuple — call `trigger(args)` to fire the request.\n\n**Rule of thumb:** Prefer `isLoading` (true only on first fetch) over\n`isFetching` (true on every fetch including background refetches) when\nrendering the initial skeleton.\n",{"id":83,"difficulty":42,"q":84,"a":85},"redux-devtools","How does Redux DevTools work with RTK and what can you inspect?","**Redux DevTools** is a browser extension that `configureStore` enables\nautomatically in development (no extra config needed). It connects to the\nstore and records every action dispatched along with a before\u002Fafter state\nsnapshot.\n\n```js\n\u002F\u002F configureStore enables DevTools automatically; disable in prod if needed\nexport const store = configureStore({\n  reducer: rootReducer,\n  devTools: process.env.NODE_ENV !== 'production', \u002F\u002F explicit control\n})\n```\n\nWith the extension open you can: **time-travel** (jump to any past state),\n**replay** the action log after a hot reload, **inspect** the diff between\nstates, and **dispatch** actions manually from the DevTools panel to test\nreducers without touching the UI.\n\nRTK also serializes the `createAsyncThunk` lifecycle actions so you see\n`user\u002FfetchById\u002Fpending`, `user\u002FfetchById\u002Ffulfilled`, etc., in the log.\n\n**Rule of thumb:** Keep action payloads serializable (plain objects, no\nclass instances, no functions) so DevTools can display and replay them\naccurately.\n",{"id":87,"difficulty":75,"q":88,"a":89},"create-entity-adapter","What is createEntityAdapter and when should you use it?","**`createEntityAdapter`** provides a standardized way to store a\ncollection of items by their ID. It creates an **`{ ids: [], entities: {} }`**\nnormalized shape and generates CRUD reducer helpers (`addOne`, `upsertMany`,\n`removeOne`, etc.).\n\n```js\nimport { createEntityAdapter, createSlice } from '@reduxjs\u002Ftoolkit'\n\nconst todosAdapter = createEntityAdapter()\n\u002F\u002F { selectAll, selectById, selectIds, selectEntities, selectTotal }\n\nconst todosSlice = createSlice({\n  name: 'todos',\n  initialState: todosAdapter.getInitialState({ status: 'idle' }), \u002F\u002F adds ids\u002Fentities keys\n  reducers: {\n    todoAdded:   todosAdapter.addOne,         \u002F\u002F pre-built reducer\n    todosLoaded: todosAdapter.setAll,\n    todoRemoved: todosAdapter.removeOne,\n  },\n  extraReducers(builder) {\n    builder.addCase(fetchTodos.fulfilled, (state, action) => {\n      todosAdapter.setAll(state, action.payload) \u002F\u002F bulk replace\n    })\n  },\n})\n\n\u002F\u002F Selectors are pre-built and accept RootState\nexport const { selectAll: selectAllTodos, selectById: selectTodoById } =\n  todosAdapter.getSelectors(state => state.todos)\n```\n\nNormalize when you have a large list and need O(1) lookups by ID (e.g.,\nselecting one item without scanning an array).\n\n**Rule of thumb:** Use `createEntityAdapter` whenever you fetch a list\nthat you later update by ID; skip it for small static lists.\n",{"id":91,"difficulty":34,"q":92,"a":93},"memoized-selectors","How do you create memoized selectors with createSelector?","**`createSelector`** (re-exported from `reselect` by RTK) takes one or\nmore **input selectors** and a **result function**. The result is\nrecomputed only when an input selector's output changes, preventing\nunnecessary re-renders from derived computations.\n\n```js\nimport { createSelector } from '@reduxjs\u002Ftoolkit'\n\nconst selectTodos = state => state.todos.items          \u002F\u002F input selector\nconst selectFilter = state => state.todos.filter        \u002F\u002F input selector\n\n\u002F\u002F result function only runs when selectTodos or selectFilter changes\nexport const selectFilteredTodos = createSelector(\n  [selectTodos, selectFilter],\n  (todos, filter) => {\n    if (filter === 'all') return todos\n    return todos.filter(t => t.status === filter)       \u002F\u002F expensive work cached\n  }\n)\n```\n\nUse it inside `useSelector`: `const todos = useSelector(selectFilteredTodos)`.\nBecause the selector reference is stable across renders (the memoized\ninstance is defined outside the component), React-Redux's equality check\nworks correctly.\n\n**Rule of thumb:** Extract a `createSelector` call whenever the selector\ncomputes a new array or object reference — otherwise every call returns a\nnew reference and causes a re-render even when the data hasn't changed.\n",{"id":95,"difficulty":34,"q":96,"a":97},"middleware-thunk-custom","How do you add custom middleware to a Redux Toolkit store?","`configureStore` adds `redux-thunk` by default. To add custom middleware,\nuse the **`middleware`** callback option which receives `getDefaultMiddleware`\nso you can keep the defaults and append your own.\n\n```js\nimport { configureStore } from '@reduxjs\u002Ftoolkit'\n\nconst loggerMiddleware = store => next => action => {\n  console.log('dispatching', action)       \u002F\u002F called before reducer\n  const result = next(action)              \u002F\u002F pass action down the chain\n  console.log('next state', store.getState())\n  return result\n}\n\nexport const store = configureStore({\n  reducer: rootReducer,\n  middleware: (getDefaultMiddleware) =>\n    getDefaultMiddleware()               \u002F\u002F keeps thunk + serializability checks\n      .concat(loggerMiddleware),         \u002F\u002F append custom middleware\n})\n```\n\nDo not replace `getDefaultMiddleware()` with an empty array unless you\nintentionally want to remove the serialization warnings and thunk support —\nthose defaults catch common bugs.\n\n**Rule of thumb:** Always call `getDefaultMiddleware()` as the base;\n`prepend` for middleware that needs to run before thunks, `concat` for\neverything else.\n",{"id":99,"difficulty":34,"q":100,"a":101},"feature-folder-structure","What is the recommended way to structure Redux slices in a large app?","The **feature folder** (or \"ducks\") pattern co-locates everything related\nto one domain: the slice, thunks, selectors, and component files. This\nkeeps related code together and avoids cross-cutting files like\n`allReducers.js`.\n\n```\nsrc\u002F\n  features\u002F\n    cart\u002F\n      cartSlice.ts       \u002F\u002F createSlice + extraReducers\n      cartThunks.ts      \u002F\u002F createAsyncThunk (or inline in slice)\n      cartSelectors.ts   \u002F\u002F createSelector calls\n      CartPage.tsx       \u002F\u002F component that uses the slice\n      cartApi.ts         \u002F\u002F RTK Query createApi (if feature-scoped)\n    user\u002F\n      userSlice.ts\n      userSelectors.ts\n      ProfilePage.tsx\n  store\u002F\n    store.ts             \u002F\u002F configureStore — imports slice reducers\n    hooks.ts             \u002F\u002F typed useAppSelector \u002F useAppDispatch\n```\n\n```js\n\u002F\u002F store\u002Fhooks.ts — typed wrappers to avoid repeating types in every component\nimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'\nimport type { RootState, AppDispatch } from '.\u002Fstore'\nexport const useAppDispatch = () => useDispatch\u003CAppDispatch>()\nexport const useAppSelector: TypedUseSelectorHook\u003CRootState> = useSelector\n```\n\n**Rule of thumb:** Keep each feature self-contained; the `store.ts` file\nshould only import reducers — no business logic lives there.\n",{"id":103,"difficulty":34,"q":104,"a":105},"when-not-to-use-redux","When should you NOT use Redux for state management?","Redux shines for **shared, cross-component global state** with complex\nupdate logic. It is overkill — and adds friction — for state that fits\nsimpler tools.\n\nAvoid Redux for:\n- **Local UI state** (modal open, form input value, hover) — `useState` is\n  sufficient and keeps the logic next to the component.\n- **Server cache state** (fetched lists, single items) — RTK Query or React\n  Query handle caching, deduplication, and refetching better than a\n  hand-rolled async slice.\n- **Small apps with one team** — Context + `useReducer` may be all you\n  need; the cognitive overhead of slices\u002Fselectors\u002Factions is not always\n  worth it.\n\n```jsx\n\u002F\u002F BAD — Redux for toggle state no other component needs\ndispatch(setModalOpen(true))\n\n\u002F\u002F GOOD — local state for isolated UI\nconst [isOpen, setIsOpen] = useState(false)\n```\n\nSymptoms of unnecessary Redux: your slices hold `isModalOpen` flags,\nyou dispatch actions from a single component and no other component\nsubscribes, or you have more action types than actual data fields.\n\n**Rule of thumb:** If no other component needs to read or write a piece\nof state, keep it local with `useState` — move it to Redux only when the\nneed actually arises.\n",{"id":107,"difficulty":75,"q":108,"a":109},"prepare-callback","What is the prepare callback in createSlice and when do you use it?","By default, action creators generated by `createSlice` accept one\nargument which becomes `action.payload`. The **`prepare` callback** lets\nyou transform the arguments — generate IDs, add timestamps, or restructure\nthe payload — before the reducer runs.\n\n```js\nimport { createSlice, nanoid } from '@reduxjs\u002Ftoolkit'\n\nconst todosSlice = createSlice({\n  name: 'todos',\n  initialState: [],\n  reducers: {\n    todoAdded: {\n      reducer(state, action) {\n        state.push(action.payload)                \u002F\u002F payload already shaped\n      },\n      prepare(text) {                            \u002F\u002F called first with the caller's args\n        return {\n          payload: {\n            id: nanoid(),                        \u002F\u002F auto-generate a unique id\n            text,\n            createdAt: new Date().toISOString(), \u002F\u002F timestamp at dispatch time\n            completed: false,\n          },\n        }\n      },\n    },\n  },\n})\n\n\u002F\u002F Caller just passes the text — ID and timestamp are added automatically\ndispatch(todoAdded('Buy milk'))\n```\n\n**Rule of thumb:** Use `prepare` to keep side effects (ID generation,\ntimestamps) out of reducers and out of components — reducers must be\npure, and components shouldn't be responsible for shaping payloads.\n",{"id":111,"difficulty":34,"q":112,"a":113},"rtk-query-mutation","How do you perform a mutation with RTK Query and update the UI after it completes?","Mutations are defined with `builder.mutation` and exposed as\n**`useXMutation`** hooks. The hook returns a `[triggerFn, resultObject]`\ntuple. Call the trigger to fire the request; the result object tracks\nthe mutation's lifecycle.\n\n```jsx\nimport { useCreatePostMutation } from '.\u002FpostsApi'\n\nfunction NewPostForm() {\n  const [createPost, { isLoading, isError, isSuccess }] = useCreatePostMutation()\n  const [title, setTitle] = useState('')\n\n  async function handleSubmit(e) {\n    e.preventDefault()\n    try {\n      await createPost({ title }).unwrap() \u002F\u002F unwrap re-throws on error\n      setTitle('')                          \u002F\u002F clear form on success\n    } catch (err) {\n      console.error('Failed to save post', err)\n    }\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput value={title} onChange={e => setTitle(e.target.value)} \u002F>\n      \u003Cbutton disabled={isLoading}>\n        {isLoading ? 'Saving…' : 'Save'}\n      \u003C\u002Fbutton>\n      {isSuccess && \u003Cp>Saved!\u003C\u002Fp>}\n      {isError   && \u003Cp>Something went wrong.\u003C\u002Fp>}\n    \u003C\u002Fform>\n  )\n}\n```\n\nIf the mutation's `invalidatesTags` overlaps with a query's `providesTags`,\nthat query automatically refetches — no manual cache update needed.\n\n**Rule of thumb:** Always call `.unwrap()` in a try\u002Fcatch so thrown errors\nsurface in your component rather than being silently swallowed.\n",{"id":115,"difficulty":42,"q":116,"a":117},"action-type-strings","How are Redux action type strings structured in Redux Toolkit?","RTK automatically names action types as **`\"sliceName\u002FreducerName\"`** for\nslice actions, and **`\"actionPrefix\u002Fpending|fulfilled|rejected\"`** for\n`createAsyncThunk` lifecycle actions.\n\n```js\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment(state) { state.value++ },\n  },\n})\n\ncounterSlice.actions.increment.type   \u002F\u002F 'counter\u002Fincrement'\ncounterSlice.actions.increment()      \u002F\u002F { type: 'counter\u002Fincrement' }\n\nconst fetchUser = createAsyncThunk('user\u002Ffetch', async (id) => { \u002F* ... *\u002F })\nfetchUser.pending.type    \u002F\u002F 'user\u002Ffetch\u002Fpending'\nfetchUser.fulfilled.type  \u002F\u002F 'user\u002Ffetch\u002Ffulfilled'\nfetchUser.rejected.type   \u002F\u002F 'user\u002Ffetch\u002Frejected'\n```\n\nBecause names are derived from the slice name, keep slice names\ndescriptive and scoped to their feature. Avoid generic names like\n`'data'` or `'state'` which will produce cryptic action logs in DevTools.\n\n**Rule of thumb:** The DevTools action log is your first debugging tool —\ntreat action type strings as documentation and name them clearly.\n",19,null,{"description":32},"Redux Toolkit interview questions — createSlice, configureStore, createAsyncThunk, RTK Query, immer mutations, and Redux DevTools for React state management.","react\u002Fstate-management\u002Fredux-toolkit","2026-06-24","McrLS2seIz5b0yfkIShk3xiUDgZ1Ep8swJGu_i4hSCw",{"id":126,"title":127,"body":128,"description":32,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":132,"navigation":37,"order":11,"path":133,"questions":134,"questionsCount":207,"related":119,"seo":208,"seoDescription":209,"stem":210,"subtopic":127,"topic":19,"topicSlug":21,"updated":123,"__hash__":211},"qa\u002Freact\u002Fstate-management\u002Fzustand.md","Zustand",{"type":29,"value":129,"toc":130},[],{"title":32,"searchDepth":11,"depth":11,"links":131},[],{},"\u002Freact\u002Fstate-management\u002Fzustand",[135,139,143,147,151,155,159,163,167,171,175,179,183,187,191,195,199,203],{"id":136,"difficulty":42,"q":137,"a":138},"what-is-zustand","What is Zustand and what problem does it solve?","**Zustand** (German for \"state\") is a lightweight React state-management\nlibrary built on a **flux-inspired one-way data flow** but with almost\nzero boilerplate. It stores state outside the React tree in a plain\nJavaScript object and lets any component subscribe to it without\nrequiring a Provider wrapper.\n\nThe problem it solves: Context re-renders every consumer when any part\nof the context value changes, and Redux forces you to write reducers,\naction types, action creators, and selectors just to add a counter.\nZustand replaces all of that with a single `create()` call.\n\n```js\nimport { create } from 'zustand'\n\nconst useCounterStore = create((set) => ({\n  count: 0,                          \u002F\u002F state\n  increment: () => set((s) => ({ count: s.count + 1 })), \u002F\u002F action\n  reset:     () => set({ count: 0 }),\n}))\n\n\u002F\u002F Consume in any component — no Provider needed\nfunction Counter() {\n  const count     = useCounterStore((s) => s.count)\n  const increment = useCounterStore((s) => s.increment)\n  return \u003Cbutton onClick={increment}>{count}\u003C\u002Fbutton>\n}\n```\n\nThe store is a React hook, state is mutable through `set`, and\ncomponents only re-render when the slice they selected changes.\n\n**Rule of thumb:** Reach for Zustand when Context performance starts\nhurting or when you need global state that isn't tied to a single\ncomponent subtree.\n",{"id":140,"difficulty":42,"q":141,"a":142},"create-store","How do you create a Zustand store with `create()`?","`create()` accepts a **callback** that receives `set` (and optionally\n`get`) and returns the initial state object. State properties and\n**actions** (functions that update state) live in the same object — there\nare no separate reducers or action types.\n\n```js\nimport { create } from 'zustand'\n\nconst useBearStore = create((set, get) => ({\n  bears: 0,\n  honey: 100,\n\n  \u002F\u002F Action using updater function — safe when next value depends on current\n  addBear: () => set((state) => ({ bears: state.bears + 1 })),\n\n  \u002F\u002F Action using partial object — fine when value is independent\n  removeAllBears: () => set({ bears: 0 }),\n\n  \u002F\u002F Action reading current state via get()\n  eatHoney: (amount) => {\n    const { honey } = get()          \u002F\u002F read without subscribing\n    if (honey >= amount) set({ honey: honey - amount })\n  },\n}))\n```\n\n`set` does a **shallow merge** (like `setState` in class components),\nso you only specify the keys you want to update. Pass `true` as the\nsecond argument (`set(fn, true)`) to replace rather than merge.\n\n**Rule of thumb:** Put both state fields and their mutation functions\nin one `create()` call — it keeps related data and logic together and\navoids cross-file action imports.\n",{"id":144,"difficulty":42,"q":145,"a":146},"selectors","Why should you use a selector when reading from a Zustand store?","Without a selector the component re-renders on **every store update**,\neven changes unrelated to what it displays. A **selector** is a function\npassed to the store hook that picks only the slice the component needs;\nZustand compares the return value between renders and skips the re-render\nif it hasn't changed.\n\n```js\nconst useBearStore = create(() => ({ bears: 0, honey: 100 }))\n\n\u002F\u002F ❌ No selector — subscribes to the whole store object.\n\u002F\u002F    Re-renders whenever bears OR honey changes.\nfunction BearsDisplay() {\n  const state = useBearStore()   \u002F\u002F returns { bears, honey }\n  return \u003Cspan>{state.bears}\u003C\u002Fspan>\n}\n\n\u002F\u002F ✅ Selector — subscribes only to bears.\n\u002F\u002F    Re-renders only when bears changes.\nfunction BearsDisplay() {\n  const bears = useBearStore((s) => s.bears)\n  return \u003Cspan>{bears}\u003C\u002Fspan>\n}\n```\n\nZustand uses `Object.is` equality by default. For selectors that return\nobjects or arrays, swap in `shallow` from `zustand\u002Fshallow` so that\na new object with the same keys doesn't trigger a re-render.\n\n**Rule of thumb:** Always select the smallest slice you need, just as\nyou would with a Redux `useSelector`.\n",{"id":148,"difficulty":42,"q":149,"a":150},"actions-inside-store","Should Zustand actions live inside or outside the store?","The **idiomatic Zustand approach** is to define actions **inside** the\n`create()` callback alongside the state they mutate. This co-location\nmeans actions can call `set` and `get` directly without being passed as\narguments, and consumers import a single hook instead of separate\naction creators.\n\n```js\nconst useUserStore = create((set, get) => ({\n  user: null,\n  isLoading: false,\n\n  \u002F\u002F Action defined inside — has closure access to set and get\n  setUser: (user) => set({ user }),\n\n  clearUser: () => set({ user: null }),\n\n  \u002F\u002F Read other state fields via get() inside an action\n  isAdmin: () => get().user?.role === 'admin',\n}))\n\n\u002F\u002F Consumers just call the action directly\nfunction LoginButton() {\n  const setUser = useUserStore((s) => s.setUser)\n  return \u003Cbutton onClick={() => setUser({ id: 1, role: 'admin' })}>\n    Login\n  \u003C\u002Fbutton>\n}\n```\n\nThe alternative — defining actions outside via `useUserStore.setState` —\nworks but scatters logic and is usually only worthwhile in very large\nstores where the slices pattern is used.\n\n**Rule of thumb:** Keep actions inside `create()` unless your store\ngrows so large that the slices pattern becomes necessary.\n",{"id":152,"difficulty":34,"q":153,"a":154},"async-actions","How do you handle async actions in Zustand?","Zustand requires **no special middleware** for async work. Because actions\nare plain functions, you can use `async\u002Fawait` directly. Call `set`\nonce or multiple times inside the async function to reflect loading,\nsuccess, and error states.\n\n```js\nconst usePostsStore = create((set) => ({\n  posts: [],\n  loading: false,\n  error: null,\n\n  fetchPosts: async () => {\n    set({ loading: true, error: null })        \u002F\u002F show spinner\n\n    try {\n      const res  = await fetch('\u002Fapi\u002Fposts')\n      const data = await res.json()\n      set({ posts: data, loading: false })     \u002F\u002F success\n    } catch (err) {\n      set({ error: err.message, loading: false }) \u002F\u002F failure\n    }\n  },\n}))\n\nfunction PostList() {\n  const { posts, loading, fetchPosts } = usePostsStore()\n  \u002F\u002F fetchPosts is a stable reference — safe in useEffect deps\n  useEffect(() => { fetchPosts() }, [fetchPosts])\n  if (loading) return \u003Cp>Loading…\u003C\u002Fp>\n  return \u003Cul>{posts.map(p => \u003Cli key={p.id}>{p.title}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n```\n\nThis is one of Zustand's biggest DX wins over Redux, where you need\n`redux-thunk` or `redux-saga` to do the same thing.\n\n**Rule of thumb:** Write async actions exactly like any `async` function\n— no middleware, no special patterns, just `await` and `set`.\n",{"id":156,"difficulty":34,"q":157,"a":158},"shallow-equality","When and why do you use `shallow` equality in Zustand?","Zustand's default comparison is `Object.is` — fine for primitives and\nstable references, but it fails when a selector returns a **new object\nor array on every call**. Import `shallow` from `zustand\u002Fshallow` and\npass it as the second argument to suppress spurious re-renders.\n\n```js\nimport { shallow } from 'zustand\u002Fshallow'\n\nconst useStore = create(() => ({\n  name: 'Alice',\n  age: 30,\n  theme: 'dark',\n}))\n\n\u002F\u002F ❌ Without shallow — new object every render → always re-renders\nfunction Profile() {\n  const { name, age } = useStore((s) => ({ name: s.name, age: s.age }))\n  return \u003Cp>{name}, {age}\u003C\u002Fp>\n}\n\n\u002F\u002F ✅ With shallow — compares keys one level deep → re-renders only\n\u002F\u002F    when name or age actually changes\nfunction Profile() {\n  const { name, age } = useStore(\n    (s) => ({ name: s.name, age: s.age }),\n    shallow,               \u002F\u002F second argument\n  )\n  return \u003Cp>{name}, {age}\u003C\u002Fp>\n}\n\n\u002F\u002F Also works for picking multiple fields as an array\nconst [name, age] = useStore((s) => [s.name, s.age], shallow)\n```\n\n**Rule of thumb:** Use `shallow` whenever your selector returns an\nobject literal or array; skip it when the selector returns a single\nprimitive or stable reference.\n",{"id":160,"difficulty":34,"q":161,"a":162},"devtools-middleware","How do you wire up Redux DevTools with Zustand?","Wrap the `create()` callback with the **`devtools`** middleware from\n`zustand\u002Fmiddleware`. Once connected, every `set` call appears as a\nnamed action in the Redux DevTools extension, and you get time-travel\ndebugging for free.\n\n```js\nimport { create }   from 'zustand'\nimport { devtools } from 'zustand\u002Fmiddleware'\n\nconst useCounterStore = create(\n  devtools(\n    (set) => ({\n      count: 0,\n      \u002F\u002F Name each action for the DevTools action log\n      increment: () => set(\n        (s) => ({ count: s.count + 1 }),\n        false,                    \u002F\u002F don't replace — merge\n        'counter\u002Fincrement',      \u002F\u002F action name shown in DevTools\n      ),\n      reset: () => set({ count: 0 }, false, 'counter\u002Freset'),\n    }),\n    { name: 'CounterStore' },     \u002F\u002F store name shown in DevTools\n  ),\n)\n```\n\nYou can pass `{ enabled: process.env.NODE_ENV === 'development' }` to\nthe `devtools` options to strip it from production bundles.\n\n**Rule of thumb:** Add `devtools` early in development — the action\nnaming discipline pays dividends when debugging complex state flows.\n",{"id":164,"difficulty":34,"q":165,"a":166},"persist-middleware","How does the `persist` middleware work in Zustand?","The **`persist`** middleware from `zustand\u002Fmiddleware` serializes store\nstate to a storage engine (localStorage by default) after every `set`\nand rehydrates it on page load. You wrap your store definition exactly\nlike `devtools`.\n\n```js\nimport { create }  from 'zustand'\nimport { persist, createJSONStorage } from 'zustand\u002Fmiddleware'\n\nconst useSettingsStore = create(\n  persist(\n    (set) => ({\n      theme: 'dark',\n      fontSize: 16,\n      setTheme:    (t) => set({ theme: t }),\n      setFontSize: (s) => set({ fontSize: s }),\n    }),\n    {\n      name:    'user-settings',          \u002F\u002F localStorage key\n      storage: createJSONStorage(() => localStorage), \u002F\u002F default\n      \u002F\u002F Persist only a subset of state\n      partialize: (state) => ({ theme: state.theme }),\n    },\n  ),\n)\n```\n\nFor SSR \u002F Next.js, swap to `sessionStorage` or a custom storage\nadapter so hydration mismatches are avoided. `partialize` lets you\nexclude sensitive or derived fields (like loading flags) from the\npersisted snapshot.\n\n**Rule of thumb:** Always use `partialize` to be explicit about which\nfields survive a page reload — persisting loading or error flags causes\nstale UI on startup.\n",{"id":168,"difficulty":34,"q":169,"a":170},"immer-middleware","How does Zustand's `immer` middleware change how you write state updates?","By default, `set` in Zustand requires you to return a **new partial\nobject**. Wrapping with the **`immer`** middleware lets you write\nmutations directly on a draft — Immer produces the new immutable state\nbehind the scenes.\n\n```js\nimport { create }  from 'zustand'\nimport { immer  }  from 'zustand\u002Fmiddleware\u002Fimmer'\n\nconst useCartStore = create(\n  immer((set) => ({\n    items: [],\n\n    \u002F\u002F Without immer you'd write: set(s => ({ items: [...s.items, item] }))\n    addItem: (item) => set((state) => {\n      state.items.push(item)           \u002F\u002F direct mutation — Immer handles it\n    }),\n\n    removeItem: (id) => set((state) => {\n      state.items = state.items.filter(i => i.id !== id)\n    }),\n\n    updateQty: (id, qty) => set((state) => {\n      const item = state.items.find(i => i.id === id)\n      if (item) item.qty = qty         \u002F\u002F nested mutation — safe with Immer\n    }),\n  })),\n)\n```\n\nWithout `immer`, updating nested objects requires spreading every layer\nmanually. With it you write imperative mutations and Immer converts them\nto structural shares.\n\n**Rule of thumb:** Add `immer` when your state has deeply nested\nstructures; skip it for flat state where spread syntax is readable\nenough.\n",{"id":172,"difficulty":34,"q":173,"a":174},"slices-pattern","What is the slices pattern in Zustand and when should you use it?","As a store grows, cramming all state and actions into one `create()` call\nbecomes unwieldy. The **slices pattern** splits the store into separate\nfunctions (slices), each responsible for a domain, and merges them in a\nsingle `create()` call.\n\n```js\n\u002F\u002F slices\u002FauthSlice.js\nexport const createAuthSlice = (set) => ({\n  user: null,\n  login:  (u) => set({ user: u }),\n  logout: ()  => set({ user: null }),\n})\n\n\u002F\u002F slices\u002FcartSlice.js\nexport const createCartSlice = (set) => ({\n  items: [],\n  addItem:    (item) => set((s) => ({ items: [...s.items, item] })),\n  clearCart:  ()     => set({ items: [] }),\n})\n\n\u002F\u002F store.js — combine slices into one store\nimport { create }          from 'zustand'\nimport { createAuthSlice } from '.\u002Fslices\u002FauthSlice'\nimport { createCartSlice } from '.\u002Fslices\u002FcartSlice'\n\nexport const useStore = create((...args) => ({\n  ...createAuthSlice(...args),\n  ...createCartSlice(...args),\n}))\n\n\u002F\u002F Consumers select from the unified store as usual\nconst user  = useStore((s) => s.user)\nconst items = useStore((s) => s.items)\n```\n\nThis is the Zustand-recommended approach for large stores. Each slice\nfile stays focused and testable independently.\n\n**Rule of thumb:** Introduce slices once your store exceeds ~5 concerns\nor the single file becomes hard to navigate.\n",{"id":176,"difficulty":34,"q":177,"a":178},"subscribe-outside-react","How do you subscribe to a Zustand store outside of a React component?","The store object created by `create()` is also a vanilla store with a\n`.subscribe()` method. You can call it in plain JS modules, Node.js\nscripts, or outside the component tree — no hooks needed.\n\n```js\nconst useStore = create((set) => ({\n  count: 0,\n  increment: () => set((s) => ({ count: s.count + 1 })),\n}))\n\n\u002F\u002F Subscribe outside React\nconst unsub = useStore.subscribe(\n  (state) => state.count,   \u002F\u002F selector — called only when count changes\n  (count, prevCount) => {\n    console.log(`count changed: ${prevCount} → ${count}`)\n    if (count >= 10) analytics.track('milestone_10')\n  },\n)\n\n\u002F\u002F Read state without subscribing\nconst current = useStore.getState().count\n\n\u002F\u002F Set state without a component\nuseStore.setState({ count: 0 })\n\n\u002F\u002F Clean up when done\nunsub()\n```\n\nThis is useful for analytics side effects, syncing with non-React\nsystems, or writing integration tests that assert state without\nrendering.\n\n**Rule of thumb:** Use `.subscribe()` \u002F `.getState()` \u002F `.setState()`\nfor interactions with the store that live outside the React lifecycle.\n",{"id":180,"difficulty":75,"q":181,"a":182},"zustand-vs-redux-toolkit","What are the trade-offs between Zustand and Redux Toolkit?","Both libraries manage global state in a React app, but they occupy\ndifferent points on the complexity\u002Fpower spectrum.\n\n| Concern | Zustand | Redux Toolkit |\n|---|---|---|\n| Bundle size | ~1 kB | ~12 kB |\n| Boilerplate | Minimal (`create` + actions) | Slices + configure + reducers |\n| Devtools | Yes (via middleware) | First-class, built-in |\n| Async | Plain async functions | `createAsyncThunk` \u002F RTK Query |\n| Data fetching | DIY or Zustand + SWR\u002FTanStack | RTK Query (cache, dedup) |\n| Middleware ecosystem | Small (devtools, persist, immer) | Large (mature) |\n| TypeScript | Good | Excellent |\n| Learning curve | Low | Medium-High |\n\n```js\n\u002F\u002F Same counter — Zustand vs RTK\n\n\u002F\u002F Zustand (6 lines)\nconst useCounter = create((set) => ({\n  value: 0,\n  increment: () => set((s) => ({ value: s.value + 1 })),\n}))\n\n\u002F\u002F Redux Toolkit (25+ lines)\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: { increment: (state) => { state.value++ } },\n})\nconst store = configureStore({ reducer: { counter: counterSlice.reducer } })\n```\n\nChoose Redux Toolkit when: you have a large team that benefits from\nstrict conventions, you need RTK Query for server-cache management, or\nyou're already invested in the Redux ecosystem. Choose Zustand when: you\nwant quick setup, smaller bundle, or a library that stays out of your way.\n\n**Rule of thumb:** Zustand for small-to-medium apps; Redux Toolkit when\nthe app is large, team is big, or RTK Query's server-state caching is\nworth the extra weight.\n",{"id":184,"difficulty":75,"q":185,"a":186},"zustand-vs-context","How does Zustand compare to the React Context API for global state?","Context and Zustand solve the same problem — sharing state across the\ntree — but with very different performance models.\n\n```jsx\n\u002F\u002F Context: every consumer re-renders when ANY part of value changes\nconst AppCtx = createContext(null)\nfunction AppProvider({ children }) {\n  const [count, setCount] = useState(0)\n  const [user,  setUser ] = useState(null)\n  \u002F\u002F ❌ Changing count re-renders components that only read user\n  return \u003CAppCtx.Provider value={{ count, setCount, user, setUser }}>\n    {children}\n  \u003C\u002FAppCtx.Provider>\n}\n\n\u002F\u002F Zustand: each component subscribes only to the slice it selects\nconst useAppStore = create((set) => ({\n  count: 0, setCount: (n) => set({ count: n }),\n  user:  null, setUser: (u) => set({ user: u }),\n}))\n\n\u002F\u002F This component NEVER re-renders when count changes\nfunction UserMenu() {\n  const user = useAppStore((s) => s.user)\n  return \u003Cspan>{user?.name}\u003C\u002Fspan>\n}\n```\n\nAdditional differences:\n\n- **Provider**: Context requires a Provider in the tree; Zustand needs\n  none.\n- **Outside React**: Zustand is accessible without hooks; Context is not.\n- **DevTools**: Zustand supports the Redux DevTools extension; Context\n  has no built-in equivalent.\n- **Bundle overhead**: Context adds zero bytes; Zustand adds ~1 kB.\n\n**Rule of thumb:** Use Context for infrequently-changing cross-cutting\nconcerns (theme, locale, auth token); switch to Zustand when multiple\ncomponents subscribe to frequently-changing shared state.\n",{"id":188,"difficulty":34,"q":189,"a":190},"testing-zustand","How do you test a Zustand store?","Zustand stores are plain JS objects — you can call actions directly and\nassert state without rendering any components.\n\n```js\n\u002F\u002F store.test.js\nimport { describe, it, expect, beforeEach } from 'vitest'\nimport { useCartStore } from '.\u002FcartStore'\n\n\u002F\u002F Reset store state between tests to prevent leakage\nbeforeEach(() => {\n  useCartStore.setState({ items: [] })  \u002F\u002F reset to initial\n})\n\ndescribe('cart store', () => {\n  it('adds an item', () => {\n    useCartStore.getState().addItem({ id: 1, name: 'Book', qty: 1 })\n    expect(useCartStore.getState().items).toHaveLength(1)\n  })\n\n  it('removes an item', () => {\n    useCartStore.setState({ items: [{ id: 1, name: 'Book', qty: 1 }] })\n    useCartStore.getState().removeItem(1)\n    expect(useCartStore.getState().items).toHaveLength(0)\n  })\n})\n```\n\nFor component tests, render normally with `@testing-library\u002Freact` —\nno mocking needed. If you want to test a component in isolation, seed\nthe store with `setState` before rendering and reset with `setState`\nin `beforeEach`.\n\n**Rule of thumb:** Test stores directly via `.getState()` \u002F `.setState()`\nfor unit tests; use the real store (no mocks) for component integration\ntests.\n",{"id":192,"difficulty":34,"q":193,"a":194},"typescript-zustand","How do you add TypeScript types to a Zustand store?","Define an **interface** (or type) for the store shape and pass it as the\ngeneric type argument to `create`. Zustand infers the rest automatically.\n\n```ts\nimport { create } from 'zustand'\n\n\u002F\u002F 1. Define the shape of state + actions\ninterface BearState {\n  bears:     number\n  honey:     number\n  addBear:   () => void\n  eatHoney:  (amount: number) => void\n}\n\n\u002F\u002F 2. Pass the type to create — the callback parameter is typed\nconst useBearStore = create\u003CBearState>()((set, get) => ({\n  bears: 0,\n  honey: 100,\n\n  addBear: () => set((s) => ({ bears: s.bears + 1 })),\n\n  eatHoney: (amount) => {\n    const { honey } = get()\n    if (honey >= amount) set({ honey: honey - amount })\n  },\n}))\n\n\u002F\u002F Selectors are fully typed — TypeScript knows bears is number\nconst bears: number = useBearStore((s) => s.bears)\n```\n\nNote the extra `()` after `create\u003CBearState>()` — this is a TypeScript\ncurrying workaround required to preserve type inference on the callback\nwithout explicitly annotating `set`.\n\n**Rule of thumb:** Always define the store interface first; it acts as\nliving documentation of every field and action the store owns.\n",{"id":196,"difficulty":42,"q":197,"a":198},"when-to-use-zustand","When should you prefer Zustand over component-local state?","**Component-local state** (`useState` \u002F `useReducer`) is the right\ndefault. Lift to Zustand only when one of these conditions holds:\n\n- **Multiple unrelated components** need to read or mutate the same\n  state (avoids prop drilling or a Context that's too coarse).\n- The state **outlives** the component (e.g., persisted cart that should\n  survive route changes).\n- You need to **read or update state outside React** (analytics,\n  WebSocket handlers, service workers).\n- **Performance**: the state changes frequently and many components\n  depend on it — Context would cause a render cascade.\n\n```jsx\n\u002F\u002F ✅ Local state — belongs to one component\nfunction SearchInput() {\n  const [query, setQuery] = useState('')\n  return \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n}\n\n\u002F\u002F ✅ Zustand — notifications consumed by Navbar, Toast, and a WebSocket\nconst useNotifStore = create((set) => ({\n  notifs: [],\n  add:    (n) => set((s) => ({ notifs: [n, ...s.notifs] })),\n  clear:  ()  => set({ notifs: [] }),\n}))\n```\n\n**Rule of thumb:** Default to `useState`; reach for Zustand when state\nneeds to be shared across component boundaries or accessed outside React.\n",{"id":200,"difficulty":34,"q":201,"a":202},"reset-store-state","What is the recommended way to reset a Zustand store to its initial state?","Keep a reference to the **initial state** and call `set` with it in a\n`reset` action. Because `set` does a shallow merge by default, passing\n`true` as the second argument does a full replace, which avoids stale\nkeys if the store shape has changed.\n\n```js\nimport { create } from 'zustand'\n\n\u002F\u002F Capture initial state outside create() so it's always available\nconst initialState = {\n  user:    null,\n  cart:    [],\n  filters: { sort: 'newest', page: 1 },\n}\n\nconst useStore = create((set) => ({\n  ...initialState,\n\n  setUser:  (u) => set({ user: u }),\n  addToCart: (item) => set((s) => ({ cart: [...s.cart, item] })),\n\n  \u002F\u002F Pass true to replace — ensures every key goes back to initial\n  resetAll: () => set(initialState, true),\n\n  \u002F\u002F Or reset only a slice\n  clearCart: () => set({ cart: [] }),\n}))\n\n\u002F\u002F Called at logout\nfunction handleLogout() {\n  useStore.getState().resetAll()\n  router.push('\u002Flogin')\n}\n```\n\nThis pattern is also the standard way to reset stores in tests:\n`useStore.setState(initialState, true)` in a `beforeEach`.\n\n**Rule of thumb:** Always store `initialState` in a const above\n`create()` so resets stay in sync with the store's definition.\n",{"id":204,"difficulty":34,"q":205,"a":206},"derived-computed-values","How do you derive computed values from a Zustand store?","Zustand has no built-in \"computed\" concept. The two idiomatic approaches\nare **inline selector logic** in `useStore()` (for simple derivations)\nand **memoized selectors** via a library like `reselect` or a plain\n`useMemo` (for expensive ones).\n\n```js\nconst useCartStore = create(() => ({\n  items: [\n    { id: 1, name: 'Book',  price: 12, qty: 2 },\n    { id: 2, name: 'Shirt', price: 30, qty: 1 },\n  ],\n}))\n\n\u002F\u002F ─── Option 1: derive inline in the selector (simple, zero extra deps) ───\nfunction CartTotal() {\n  const total = useCartStore((s) =>\n    s.items.reduce((sum, item) => sum + item.price * item.qty, 0)\n  )\n  return \u003Cstrong>Total: ${total}\u003C\u002Fstrong>\n}\n\u002F\u002F Re-renders only when items changes; computation runs on every render\n\n\u002F\u002F ─── Option 2: store a selector factory (avoid recompute if items unchanged)\nimport { useMemo } from 'react'\nfunction CartSummary() {\n  const items = useCartStore((s) => s.items)\n  const total = useMemo(\n    () => items.reduce((sum, i) => sum + i.price * i.qty, 0),\n    [items],  \u002F\u002F only recalculates when items reference changes\n  )\n  return \u003Cp>{items.length} items — ${total}\u003C\u002Fp>\n}\n```\n\nStoring derived values inside the store itself is an anti-pattern —\nthey can get out of sync if the source data is updated directly.\n\n**Rule of thumb:** Keep derived values out of the store; compute them\nin selectors or `useMemo` so they're always consistent with their\nsource.\n",18,{"description":32},"Zustand interview questions — create store, selectors, async actions, devtools, persist middleware, slices pattern, and Zustand vs Redux comparison.","react\u002Fstate-management\u002Fzustand","s8StsaWWxbjyaa7HDzK8qvBUTJnuw_BkENdyoqNrypU",{"id":213,"title":214,"body":215,"description":32,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":219,"navigation":37,"order":220,"path":221,"questions":222,"questionsCount":207,"related":119,"seo":294,"seoDescription":295,"stem":296,"subtopic":297,"topic":19,"topicSlug":21,"updated":123,"__hash__":298},"qa\u002Freact\u002Fstate-management\u002Fcontext-vs-redux.md","Context Vs Redux",{"type":29,"value":216,"toc":217},[],{"title":32,"searchDepth":11,"depth":11,"links":218},[],{},3,"\u002Freact\u002Fstate-management\u002Fcontext-vs-redux",[223,227,231,235,239,243,247,251,255,258,262,266,270,274,278,282,286,290],{"id":224,"difficulty":42,"q":225,"a":226},"context-api-what-is-it","What is the Context API and what is its primary use case?","The **Context API** is React's built-in mechanism for sharing values across\na component tree without passing props through every intermediate layer. You\ncreate a context with `createContext`, wrap a subtree in its `Provider`, and\nany descendant can read the value with `useContext`.\n\n```jsx\nconst ThemeContext = createContext('light')  \u002F\u002F default value\n\nfunction App() {\n  return (\n    \u003CThemeContext.Provider value=\"dark\">\n      \u003CLayout \u002F>                 {\u002F* no prop threading needed *\u002F}\n    \u003C\u002FThemeContext.Provider>\n  )\n}\n\nfunction Button() {\n  const theme = useContext(ThemeContext)  \u002F\u002F reads 'dark' directly\n  return \u003Cbutton className={theme}>Click\u003C\u002Fbutton>\n}\n```\n\nIts primary use case is **ambient, infrequently-updated global data** —\nthe authenticated user, current locale, colour theme, or feature flags.\nThese values are read often but change rarely, so the re-render cost is\nlow.\n\n**Rule of thumb:** Context is a dependency-injection mechanism, not a\nstate manager. Reach for it when you want to avoid prop drilling, not when\nyou need a sophisticated state architecture.\n",{"id":228,"difficulty":42,"q":229,"a":230},"context-vs-redux-mental-model","What is the core mental model difference between Context and Redux?","**Context** is a *transport layer* — it teleports a value from a Provider\nto any consumer without passing props. It owns no opinion about how state\nis structured or updated; you still call `useState`\u002F`useReducer` yourself.\n\n**Redux** is a *state architecture* — a single, centralised store with\nstrict rules: state is read-only, updates happen through serialisable\n**actions**, and **reducers** are pure functions that produce the next\nstate. Middleware, selectors, and DevTools are first-class concepts.\n\n```js\n\u002F\u002F Context — you manage the state, Context just shares it\nconst [user, setUser] = useState(null)\n\u003CUserContext.Provider value={{ user, setUser }}>...\u003C\u002FUserContext.Provider>\n\n\u002F\u002F Redux — the store manages everything; components just dispatch\u002Fselect\ndispatch({ type: 'auth\u002Flogin', payload: { id: 1, name: 'Ada' } })\nconst user = useSelector(state => state.auth.user)\n```\n\nThink of it this way: Context answers \"how do I share state?\" — Redux\nanswers \"how do I manage state?\"\n\n**Rule of thumb:** If you find yourself bolting a reducer, middleware, and\nselector logic onto Context, you've reinvented Redux — just use Redux.\n",{"id":232,"difficulty":34,"q":233,"a":234},"context-re-render-problem","Why does Context cause excessive re-renders and when does it become a problem?","Every component that calls `useContext(MyContext)` re-renders **whenever\nthe Provider's `value` reference changes** — regardless of whether the\nspecific slice of that value the component uses actually changed. React\nhas no built-in selector granularity for Context.\n\n```jsx\nconst AppContext = createContext()\n\nfunction Provider({ children }) {\n  const [user, setUser] = useState(null)\n  const [cart, setCart] = useState([])\n\n  \u002F\u002F New object every render — both user AND cart consumers re-render\n  \u002F\u002F even if only `cart` changed\n  return (\n    \u003CAppContext.Provider value={{ user, setUser, cart, setCart }}>\n      {children}\n    \u003C\u002FAppContext.Provider>\n  )\n}\n\nfunction UserBadge() {\n  const { user } = useContext(AppContext)  \u002F\u002F re-renders on every cart update too\n  return \u003Cspan>{user?.name}\u003C\u002Fspan>\n}\n```\n\nThis becomes a real problem when the context value updates **frequently**\n(e.g. every keystroke, every animation frame, a live-updating cart) and\nmany components are subscribed.\n\n**Rule of thumb:** Context re-render overhead is acceptable for values\nthat change a few times per session; it becomes a bottleneck for values\nthat change multiple times per second or that are consumed by dozens of\ncomponents.\n",{"id":236,"difficulty":42,"q":237,"a":238},"context-good-fit","When is Context sufficient — what kinds of state does it handle well?","Context shines for **infrequently-updated, app-wide ambient data** where\nyou need broad access but not granular subscriptions:\n\n- **Auth user** — set on login\u002Flogout, read in many places, almost never\n  changes during a session.\n- **Theme \u002F colour mode** — toggled once in a while, consumed by many\n  components for styling.\n- **Locale \u002F i18n** — changes only when the user switches language.\n- **Feature flags** — loaded once at startup.\n- **UI preferences** — sidebar collapsed, modal stack.\n\n```jsx\n\u002F\u002F A practical, well-scoped auth context\nconst AuthContext = createContext(null)\n\nexport function AuthProvider({ children }) {\n  const [user, setUser] = useState(null)\n  const login  = useCallback(async (creds) => { \u002F* ... *\u002F }, [])\n  const logout = useCallback(() => setUser(null), [])\n\n  \u002F\u002F Memoised so reference only changes when user changes\n  const value = useMemo(() => ({ user, login, logout }), [user, login, logout])\n  return \u003CAuthContext.Provider value={value}>{children}\u003C\u002FAuthContext.Provider>\n}\n```\n\n**Rule of thumb:** If the value changes less than once per user action,\nContext is probably the right tool.\n",{"id":240,"difficulty":34,"q":241,"a":242},"redux-when-necessary","When does an app outgrow Context and actually need Redux?","Redux earns its keep when several of these conditions are true:\n\n1. **High-frequency updates** — shopping carts, real-time feeds, form\n   state with many interdependent fields.\n2. **Complex async** — sequences of API calls with loading\u002Ferror\u002Fretry\n   states that need to be cancelled, debounced, or coordinated.\n3. **Cross-cutting derived state** — selectors that combine slices from\n   multiple parts of the store.\n4. **Large team** — enforced conventions (action types, reducer\n   boundaries) prevent accidental coupling.\n5. **Audit \u002F replay requirements** — you need a full log of every state\n   change for debugging or analytics.\n\n```js\n\u002F\u002F Redux Toolkit slice — clear action\u002Freducer contract\nconst cartSlice = createSlice({\n  name: 'cart',\n  initialState: { items: [], status: 'idle' },\n  reducers: {\n    addItem:    (state, action) => { state.items.push(action.payload) },\n    removeItem: (state, action) => { state.items = state.items.filter(i => i.id !== action.payload) },\n  },\n})\n```\n\n**Rule of thumb:** If you start adding \"Redux-like\" patterns to Context\n(reducers, action types, middleware), stop and use Redux Toolkit instead.\n",{"id":244,"difficulty":34,"q":245,"a":246},"splitting-contexts","How do you split contexts to reduce unnecessary re-renders?","The key insight is that each `useContext` call subscribes to **one**\ncontext. If you split your monolithic context into smaller, focused\ncontexts, a component that consumes only one will not re-render when\nanother changes.\n\n```jsx\n\u002F\u002F ❌ One big context — UserBadge re-renders on every cart change\n\u003CAppContext.Provider value={{ user, cart }}>\n\n\u002F\u002F ✅ Separate contexts — UserBadge only subscribes to UserContext\nconst UserContext = createContext(null)\nconst CartContext = createContext(null)\n\nfunction Providers({ children }) {\n  const [user] = useState(null)\n  const [cart, setCart] = useState([])\n  return (\n    \u003CUserContext.Provider value={user}>\n      \u003CCartContext.Provider value={{ cart, setCart }}>\n        {children}\n      \u003C\u002FCartContext.Provider>\n    \u003C\u002FUserContext.Provider>\n  )\n}\n\nfunction UserBadge() {\n  const user = useContext(UserContext)  \u002F\u002F never re-renders for cart updates\n  return \u003Cspan>{user?.name}\u003C\u002Fspan>\n}\n```\n\nA good heuristic: each context should have a single reason to change.\nIf you find yourself subscribing to the same context from unrelated\ncomponents, split it.\n\n**Rule of thumb:** One concern per context; keep Providers lean so their\nvalue references are stable.\n",{"id":248,"difficulty":34,"q":249,"a":250},"context-usereducer-pattern","How does Context + useReducer work as a lightweight Redux alternative?","Combining `useReducer` with Context gives you Redux's core pattern —\na single dispatch function, a reducer, and a read-only state view — with\nno external dependency.\n\n```jsx\nconst CounterStateContext   = createContext(null)\nconst CounterDispatchContext = createContext(null)  \u002F\u002F separate so consumers that only dispatch don't re-render on state changes\n\nfunction counterReducer(state, action) {\n  switch (action.type) {\n    case 'increment': return { count: state.count + 1 }\n    case 'decrement': return { count: state.count - 1 }\n    default: throw new Error(`Unknown action: ${action.type}`)\n  }\n}\n\nexport function CounterProvider({ children }) {\n  const [state, dispatch] = useReducer(counterReducer, { count: 0 })\n  return (\n    \u003CCounterStateContext.Provider value={state}>\n      \u003CCounterDispatchContext.Provider value={dispatch}>\n        {children}\n      \u003C\u002FCounterDispatchContext.Provider>\n    \u003C\u002FCounterStateContext.Provider>\n  )\n}\n\n\u002F\u002F Consuming components\nconst { count } = useContext(CounterStateContext)   \u002F\u002F reads state\nconst dispatch  = useContext(CounterDispatchContext) \u002F\u002F stable reference, never re-renders for state changes\n```\n\nThis pattern works well for small-to-medium apps. What it still lacks:\nmiddleware, DevTools, and selector memoisation.\n\n**Rule of thumb:** Context + useReducer is a great choice up to ~5 reducers;\nbeyond that the manual wiring overhead starts to rival Redux Toolkit's setup cost.\n",{"id":252,"difficulty":75,"q":253,"a":254},"redux-middleware","What is Redux middleware and why can't Context replicate it natively?","**Middleware** sits between `dispatch` and the reducer, intercepts every\naction, and can transform, delay, cancel, or trigger side effects before\npassing the action along. The two dominant middleware are `redux-thunk`\n(async action creators) and `redux-saga` (complex async flows with\ngenerators).\n\n```js\n\u002F\u002F redux-thunk: dispatch a function instead of a plain object\nexport const fetchUser = (id) => async (dispatch, getState) => {\n  dispatch({ type: 'users\u002FfetchStart' })\n  try {\n    const user = await api.getUser(id)\n    dispatch({ type: 'users\u002FfetchSuccess', payload: user })\n  } catch (err) {\n    dispatch({ type: 'users\u002FfetchFailure', payload: err.message })\n  }\n}\n\n\u002F\u002F RTK wraps this with createAsyncThunk for zero boilerplate\nexport const fetchUser = createAsyncThunk('users\u002Ffetch', (id) => api.getUser(id))\n```\n\nContext has no equivalent. You can put an async function in a Context\nvalue, but you lose the action-log, DevTools integration, cancellation\ntokens, and the ability to chain\u002Fcancel at the middleware layer. Replicating\nsaga-level coordination (race conditions, debounce, takeLatest) in Context\nrequires re-implementing what middleware already provides.\n\n**Rule of thumb:** The moment you need async sequencing beyond a single\n`async\u002Fawait` call, Redux middleware (or React Query for data fetching)\npays for itself immediately.\n",{"id":83,"difficulty":42,"q":256,"a":257},"What does Redux DevTools offer and why is it valuable?","**Redux DevTools** is a browser extension (and built-in RTK feature) that\ngives you a full observable record of your application's state history.\nKey features:\n\n- **Action log** — every dispatched action listed in order.\n- **State diff** — exactly what changed in the store for each action.\n- **Time-travel debugging** — jump to any past state by clicking an entry\n  in the log; the UI re-renders as if it's at that point in time.\n- **Action replay** — replay a sequence of actions to reproduce a bug.\n- **Import\u002Fexport state** — share a state snapshot with a team member.\n\n```js\n\u002F\u002F RTK configures DevTools automatically in development\nimport { configureStore } from '@reduxjs\u002Ftoolkit'\n\nconst store = configureStore({\n  reducer: rootReducer,\n  \u002F\u002F devTools: true by default in dev, false in production\n})\n```\n\nContext has no equivalent. A console.log in a reducer or a React DevTools\ncomponent inspection is the closest you can get, but neither gives you\ntime-travel or serialisable action history.\n\n**Rule of thumb:** If you've ever said \"I wish I could rewind to see what\ncaused this bug,\" that's the DevTools use case — and it's only available\nwith Redux.\n",{"id":259,"difficulty":34,"q":260,"a":261},"boilerplate-context-vs-rtk","How does the boilerplate of Context compare to Redux Toolkit?","Modern **Redux Toolkit (RTK)** has dramatically closed the boilerplate gap.\nCreating a slice, wiring a store, and connecting a component now requires\nfewer files than a well-structured Context setup.\n\n```js\n\u002F\u002F RTK: one createSlice call replaces action types + action creators + reducer\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },  \u002F\u002F Immer under the hood — mutate safely\n    decrement: state => { state.value -= 1 },\n  },\n})\n\nexport const { increment, decrement } = counterSlice.actions\nexport default counterSlice.reducer\n```\n\n```jsx\n\u002F\u002F Context equivalent needs: createContext, Provider component,\n\u002F\u002F useState or useReducer, custom hooks, and manual memoisation —\n\u002F\u002F often spread across 3–4 files for a non-trivial piece of state.\n```\n\nThe honest comparison in 2024: RTK setup is ~20 lines; a well-structured\nContext + useReducer setup for the same feature is similar or larger.\nThe Redux tax is now mostly in learning the mental model, not in line count.\n\n**Rule of thumb:** Don't avoid Redux for boilerplate reasons; the RTK API\nis concise. Avoid it because your state needs are genuinely simple.\n",{"id":263,"difficulty":34,"q":264,"a":265},"usememo-context-value","How does useMemo help with Context performance and when is it required?","Every time a Provider's parent re-renders, its `value` prop is a new\nobject literal — which triggers re-renders in all consumers even if the\ndata is identical. **`useMemo`** stabilises the reference so consumers\nonly re-render when the underlying data actually changes.\n\n```jsx\nfunction UserProvider({ children }) {\n  const [user, setUser]   = useState(null)\n  const [token, setToken] = useState(null)\n\n  \u002F\u002F ❌ New object every render, even if user\u002Ftoken haven't changed\n  \u002F\u002F return \u003CCtx.Provider value={{ user, setUser, token, setToken }}>\n\n  \u002F\u002F ✅ Stable reference — consumers re-render only when user or token changes\n  const value = useMemo(\n    () => ({ user, setUser, token, setToken }),\n    [user, token]  \u002F\u002F setUser\u002FsetToken are stable (from useState)\n  )\n\n  return \u003CUserContext.Provider value={value}>{children}\u003C\u002FUserContext.Provider>\n}\n```\n\nWhen is it required? Whenever the Provider's parent can re-render for\nreasons unrelated to the context value — e.g., it lives at the top of the\ntree where any ancestor state change would cascade down.\n\n**Rule of thumb:** Always wrap a Context value object in `useMemo` if\nthe Provider is not at the very root of the tree; skip it only when\nthe Provider is a static singleton that never re-renders.\n",{"id":267,"difficulty":75,"q":268,"a":269},"context-no-selector-granularity","What is the \"no selector granularity\" limitation of Context?","When you call `useContext(MyContext)`, you subscribe to the **entire\ncontext value**. There is no built-in way to say \"re-render only if\n`state.count` changed\" — you always get the whole object, and any change\nto any part of it triggers a re-render.\n\n```jsx\n\u002F\u002F With Redux + useSelector — granular subscription\nconst count = useSelector(state => state.counter.count)\n\u002F\u002F Only re-renders when state.counter.count changes\n\n\u002F\u002F With Context — no selector, full value\nconst { count, user, cart, theme } = useContext(AppContext)\n\u002F\u002F Re-renders whenever ANY of these change\n```\n\nRedux's `useSelector` uses **strict equality** (or a custom comparator)\non the selector's return value. If `state.counter.count` hasn't changed,\nthe component skips re-rendering even if other parts of the store did.\nLibraries like `use-context-selector` backport this behaviour to Context,\nbut at that point you're carrying the complexity of Redux without its\necosystem.\n\n**Rule of thumb:** If you need subscription granularity finer than \"the\nwhole context value\", Redux (or Zustand\u002FJotai) is the right tool.\n",{"id":271,"difficulty":34,"q":272,"a":273},"zustand-jotai-middle-ground","Where do Zustand and Jotai fit between Context and Redux?","**Zustand** and **Jotai** occupy the pragmatic middle ground: they provide\ngranular subscriptions and minimal boilerplate without Redux's architectural\noverhead.\n\n**Zustand** is a single-store solution. You define state and actions together\nin a `create` call; components subscribe to slices with a selector:\n\n```js\nimport { create } from 'zustand'\n\nconst useStore = create(set => ({\n  count: 0,\n  increment: () => set(state => ({ count: state.count + 1 })),\n}))\n\nfunction Counter() {\n  const count     = useStore(state => state.count)      \u002F\u002F granular — re-renders only when count changes\n  const increment = useStore(state => state.increment)  \u002F\u002F stable reference\n  return \u003Cbutton onClick={increment}>{count}\u003C\u002Fbutton>\n}\n```\n\n**Jotai** uses an **atomic** model: each `atom` is an independent piece of\nstate. Components subscribe to individual atoms, so updates are naturally\nscoped to only the atoms a component reads.\n\nNeither has Redux DevTools baked in (though Zustand has a devtools\nmiddleware) or first-class saga\u002Fthunk support.\n\n**Rule of thumb:** Reach for Zustand when Context starts hurting and Redux\nfeels like overkill; reach for Jotai when you want fine-grained atomic\nstate without a centralised store.\n",{"id":275,"difficulty":34,"q":276,"a":277},"testing-context-vs-redux","How does testing differ between Context-based state and Redux?","**Context** — wrap the component under test in its Provider and pass a\ncontrolled value. No special setup, no mock store.\n\n```jsx\n\u002F\u002F Testing a component that reads from Context\ntest('shows user name', () => {\n  render(\n    \u003CUserContext.Provider value={{ user: { name: 'Ada' } }}>\n      \u003CUserBadge \u002F>\n    \u003C\u002FUserContext.Provider>\n  )\n  expect(screen.getByText('Ada')).toBeInTheDocument()\n})\n```\n\n**Redux** — use RTK's `configureStore` with a real (or pre-populated) store,\nor use `renderWithProviders` from `@reduxjs\u002Ftoolkit\u002Freact`:\n\n```jsx\ntest('increments counter', async () => {\n  const { store } = renderWithProviders(\u003CCounter \u002F>)\n  await userEvent.click(screen.getByRole('button', { name: \u002Fincrement\u002Fi }))\n  expect(store.getState().counter.value).toBe(1)\n})\n```\n\nRedux gives you direct access to `store.getState()` and `store.dispatch()`\nin tests, making it easy to assert against the store as well as the UI.\nContext is simpler to mock but harder to inspect state independently of\nthe rendered output.\n\n**Rule of thumb:** Context tests are simpler to write; Redux tests are more\npowerful for asserting state transitions independently from rendering.\n",{"id":279,"difficulty":34,"q":280,"a":281},"multiple-contexts-vs-one-store","How do multiple small contexts compare to one large Redux store?","**Multiple contexts** let you co-locate state near the component subtrees\nthat use it. Each context has an independent update cycle, so they don't\ninterfere with each other. The cost is coordination: sharing data across\ncontexts requires lifting it to a common ancestor, which can recreate\nprop-drilling at the context level.\n\n```jsx\n\u002F\u002F Fine — independent, non-overlapping concerns\n\u003CAuthContext.Provider value={auth}>\n  \u003CThemeContext.Provider value={theme}>\n    \u003CNotificationsContext.Provider value={notifications}>\n      \u003CApp \u002F>\n    \u003C\u002FNotificationsContext.Provider>\n  \u003C\u002FThemeContext.Provider>\n\u003C\u002FAuthContext.Provider>\n```\n\n**One Redux store** centralises everything. Any slice can read from any\nother slice in a selector. The downside is that everything is global by\ndefault — there's no encapsulation boundary.\n\nRTK's `createSlice` recovers some modularity (each slice is a self-contained\nmodule), but ultimately they all merge into one global state tree.\n\n**Rule of thumb:** Multiple contexts for independent concerns; a Redux store\nwhen slices need to derive state from each other or share middleware.\n",{"id":283,"difficulty":42,"q":284,"a":285},"global-vs-local-state","How should you decide between global state (Context\u002FRedux) and local component state?","A useful mental checklist — before reaching for a global state solution,\nask these questions:\n\n```\nDoes only ONE component need this state?     → useState, keep it local\nDo a FEW co-located components need it?      → lift state to nearest common ancestor\nDo MANY unrelated components need it?        → Context (if infrequent updates)\nIs it updated FREQUENTLY or needs middleware? → Redux \u002F Zustand\nIs it SERVER state (loading, caching)?        → React Query \u002F SWR\n```\n\nOver-globalising state is a common mistake: putting form field values,\nmodal-open flags, and tooltip visibility into Redux adds noise to the\naction log and couples unrelated parts of the app. These are **ephemeral\nUI state** — local `useState` is almost always the right choice.\n\n**Rule of thumb:** Default to local state; promote to global only when\nsharing across unrelated subtrees becomes genuinely painful.\n",{"id":287,"difficulty":75,"q":288,"a":289},"context-ssr-nextjs-gotchas","What are the Context gotchas in SSR \u002F Next.js?","In a **Server Component** world (Next.js App Router), Context Providers\nmust be **Client Components** — they rely on React's runtime reconciler,\nwhich only runs in the browser. Trying to render a Context Provider in a\nServer Component throws an error.\n\n```jsx\n\u002F\u002F app\u002Fproviders.tsx — must be a Client Component\n'use client'\nimport { ThemeContext } from '.\u002FThemeContext'\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  const [theme, setTheme] = useState('dark')\n  return (\n    \u003CThemeContext.Provider value={{ theme, setTheme }}>\n      {children}\n    \u003C\u002FThemeContext.Provider>\n  )\n}\n\n\u002F\u002F app\u002Flayout.tsx — Server Component that wraps with Providers\nimport { Providers } from '.\u002Fproviders'\nexport default function RootLayout({ children }) {\n  return \u003Chtml>\u003Cbody>\u003CProviders>{children}\u003C\u002FProviders>\u003C\u002Fbody>\u003C\u002Fhtml>\n}\n```\n\nAdditional gotchas:\n- **Hydration mismatch** — if Context initialises from browser-only APIs\n  (localStorage, cookies), the server-rendered HTML will differ from the\n  client hydration pass, causing warnings.\n- **Per-request isolation** — in traditional SSR, a singleton Context\n  module is shared across requests. Each request must create its own\n  Provider instance; never store per-user data in a module-level variable.\n\n**Rule of thumb:** In Next.js App Router, keep Providers in a single\n`'use client'` file at the root layout and pass server-fetched data in\nas props rather than initialising them from async operations inside the\nProvider.\n",{"id":291,"difficulty":42,"q":292,"a":293},"context-vs-redux-summary","How do you summarise the choice between Context and Redux for an interviewer?","A strong interview answer covers three axes: **frequency of updates**,\n**complexity of async logic**, and **team\u002Fscale requirements**.\n\n```\nContext is right when:\n  ✓ State changes infrequently (theme, auth, locale)\n  ✓ No async middleware needed\n  ✓ Small-to-medium app or isolated subtree\n  ✓ You want zero extra dependencies\n\nRedux (RTK) is right when:\n  ✓ State updates frequently or drives complex UI\n  ✓ Async sequences: loading\u002Ferror states, cancellation, polling\n  ✓ Derived data across multiple slices (selectors)\n  ✓ Large team needs enforced conventions\n  ✓ Time-travel debugging has real value\n\nMiddle ground (Zustand\u002FJotai):\n  ✓ Granular subscriptions without Redux boilerplate\n  ✓ Stores need to be shared but you don't want a full Redux setup\n```\n\nInterviewers want to hear that you understand the trade-offs, not just\n\"Context is simpler.\" Mention re-render behaviour, selector granularity,\nand middleware — those are the differentiators that signal real experience.\n\n**Rule of thumb:** Start with local state, promote to Context for ambient\nglobal data, reach for Redux when your state has behaviour (async, derived\ndata, audit trail) that Context can't model cleanly.\n",{"description":32},"Context vs Redux interview questions — when to use each, performance trade-offs, re-render behavior, middleware, devtools, and scaling considerations.","react\u002Fstate-management\u002Fcontext-vs-redux","Context vs Redux","Z9H1j547sQNnxtFWfskeekVWpzfniphJMcA-f2UPPIA",{"id":300,"title":301,"body":302,"description":32,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":306,"navigation":37,"order":307,"path":308,"questions":309,"questionsCount":390,"related":119,"seo":391,"seoDescription":392,"stem":393,"subtopic":394,"topic":19,"topicSlug":21,"updated":123,"__hash__":395},"qa\u002Freact\u002Fstate-management\u002Fasync-state-react-query.md","Async State React Query",{"type":29,"value":303,"toc":304},[],{"title":32,"searchDepth":11,"depth":11,"links":305},[],{},4,"\u002Freact\u002Fstate-management\u002Fasync-state-react-query",[310,314,318,322,326,330,334,338,342,346,350,354,358,362,366,370,374,378,382,386],{"id":311,"difficulty":42,"q":312,"a":313},"what-is-react-query","What is TanStack Query (React Query) and what problem does it solve?","**TanStack Query** (formerly React Query) is a library for managing\n**server state** in React apps. It solves the boilerplate problem of\nusing `useState` + `useEffect` to fetch data: no more manual loading\nflags, error states, cache invalidation, or race-condition handling.\n\n```jsx\n\u002F\u002F Without React Query — manual state machine\nconst [data, setData] = useState(null)\nconst [loading, setLoading] = useState(false)\nconst [error, setError] = useState(null)\n\nuseEffect(() => {\n  setLoading(true)\n  fetch('\u002Fapi\u002Fuser')\n    .then(r => r.json())\n    .then(d => { setData(d); setLoading(false) })\n    .catch(e => { setError(e); setLoading(false) })\n}, [])\n\n\u002F\u002F With React Query — three lines replace the whole block\nconst { data, isLoading, error } = useQuery({\n  queryKey: ['user'],\n  queryFn: () => fetch('\u002Fapi\u002Fuser').then(r => r.json()),\n})\n```\n\nReact Query also handles caching, background refetching, deduplication of\nconcurrent requests, and retry logic — all out of the box.\n\n**Rule of thumb:** if the data lives on a server and you want it fresh in\nthe UI, React Query is the right tool; it is not for ephemeral UI state\nlike modal toggles.\n",{"id":315,"difficulty":42,"q":316,"a":317},"usequery-basics","What are the three required \u002F most-used options for useQuery?","`useQuery` needs at minimum a **queryKey** and a **queryFn**. The third\ncommon option is `staleTime`.\n\n```jsx\nimport { useQuery } from '@tanstack\u002Freact-query'\n\nfunction UserProfile({ userId }) {\n  const { data, isLoading, isError, error } = useQuery({\n    queryKey: ['user', userId],      \u002F\u002F cache key — array form\n    queryFn: () =>\n      fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(r => r.json()), \u002F\u002F async fetcher\n    staleTime: 1000 * 60 * 5,        \u002F\u002F treat data as fresh for 5 min\n  })\n\n  if (isLoading) return \u003CSpinner \u002F>\n  if (isError)   return \u003Cp>{error.message}\u003C\u002Fp>\n  return \u003Ch1>{data.name}\u003C\u002Fh1>\n}\n```\n\nThe hook returns many flags — `isPending`, `isFetching`, `isSuccess`,\n`isError`, `isStale` — plus `data`, `error`, and `refetch`. `isLoading`\nis true only on the very first fetch (no cached data + fetching); use\n`isFetching` to show a background-refetch spinner.\n\n**Rule of thumb:** always destructure only what you need; the full return\nobject has ~25 properties.\n",{"id":319,"difficulty":42,"q":320,"a":321},"query-keys-importance","Why do query keys matter in React Query?","**Query keys** are the primary key for the query cache. Every unique key\nmaps to its own cache entry. React Query uses them to deduplicate\nin-flight requests, invalidate targeted entries after mutations, and\nre-run queries when the key changes.\n\n```jsx\n\u002F\u002F Different keys → different cache entries\nuseQuery({ queryKey: ['user', 1], queryFn: ... }) \u002F\u002F cache: user\u002F1\nuseQuery({ queryKey: ['user', 2], queryFn: ... }) \u002F\u002F cache: user\u002F2\n\n\u002F\u002F Key changes when userId changes → automatic re-fetch\nuseQuery({ queryKey: ['user', userId], queryFn: fetchUser })\n\n\u002F\u002F Invalidate all 'user' queries after an update\nqueryClient.invalidateQueries({ queryKey: ['user'] })\n```\n\nKeys are serialized deeply (arrays and objects both work), so\n`['users', { status: 'active' }]` and `['users', { status: 'inactive' }]`\nare independent cache slots. Treat keys like a URL: they should\nuniquely and fully describe the data being fetched.\n\n**Rule of thumb:** always include every variable the query depends on\n(IDs, filters, page numbers) in the key — never leave a dependency out.\n",{"id":323,"difficulty":34,"q":324,"a":325},"stale-time-vs-cache-time","What is the difference between staleTime and gcTime (cacheTime)?","**`staleTime`** controls how long fetched data is considered fresh.\nDuring that window React Query will serve cached data without\nre-fetching. Default is `0` (immediately stale).\n\n**`gcTime`** (formerly `cacheTime`) controls how long **unused** cached\ndata is kept in memory before garbage collection. Default is 5 minutes.\n\n```jsx\nuseQuery({\n  queryKey: ['posts'],\n  queryFn: fetchPosts,\n  staleTime: 1000 * 60,      \u002F\u002F data is fresh for 1 min\n  gcTime: 1000 * 60 * 10,    \u002F\u002F cache kept 10 min after last observer\n})\n\u002F\u002F 0–60 s:  component mounts → serves cache, no network request\n\u002F\u002F 60+ s:   data is stale → re-fetch in background on next mount\u002Ffocus\n\u002F\u002F unmount: cache kept 10 min then collected\n```\n\nA high `staleTime` with a long `gcTime` means near-instant navigation\nbetween pages without spinners, while the data stays reasonably fresh.\n\n**Rule of thumb:** `staleTime` = \"how often can I tolerate stale data\";\n`gcTime` = \"how long should I cache data after nobody is watching it.\"\n",{"id":327,"difficulty":34,"q":328,"a":329},"background-refetching","When does React Query automatically refetch data in the background?","React Query triggers a background refetch — silently updating stale data\nwithout showing a spinner — in four situations by default:\n\n```jsx\nuseQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n  \u002F\u002F all true by default:\n  refetchOnWindowFocus: true,     \u002F\u002F tab gets focus again\n  refetchOnMount: true,           \u002F\u002F new component instance mounts\n  refetchOnReconnect: true,       \u002F\u002F network comes back online\n  staleTime: 0,                   \u002F\u002F data is immediately stale\n})\n```\n\nDuring a background refetch `isFetching` is `true` but `isLoading`\nremains `false` (cached data still displayed). This lets you show a\nsubtle \"refreshing\" indicator without blanking the screen.\n\nYou can also trigger refetches manually via `refetch()` or via\n`queryClient.invalidateQueries({ queryKey: ['todos'] })`.\n\n**Rule of thumb:** increase `staleTime` to reduce unnecessary network\ntraffic; set `refetchOnWindowFocus: false` in forms or dashboards where\na mid-edit refresh would confuse users.\n",{"id":331,"difficulty":42,"q":332,"a":333},"loading-error-success-states","How do you handle loading, error, and success states with useQuery?","`useQuery` returns boolean flags for every state in the request lifecycle.\nUse them to render the right UI slice.\n\n```jsx\nconst { data, isLoading, isError, error, isSuccess } = useQuery({\n  queryKey: ['profile'],\n  queryFn: fetchProfile,\n})\n\n\u002F\u002F isLoading: true only on first fetch (no cached data)\nif (isLoading) return \u003CSkeleton \u002F>\n\n\u002F\u002F isError: true when queryFn threw or rejected\nif (isError) return \u003CAlert message={error.message} \u002F>\n\n\u002F\u002F isSuccess: true once data is available (even stale)\nreturn \u003CProfile user={data} \u002F>\n```\n\nFor background refreshes where you want to keep showing old data while\nnew data loads, check `isFetching` alongside `isSuccess`.\n\n**Rule of thumb:** check `isLoading` for the empty-state skeleton and\n`isError` for the error boundary — never check `!data` as a loading\nproxy, because stale data can exist alongside a fresh fetch.\n",{"id":335,"difficulty":34,"q":336,"a":337},"usemutation-basics","How does useMutation work and what callbacks does it provide?","**`useMutation`** handles write operations (POST, PUT, DELETE). Unlike\n`useQuery` it does not run automatically — you call `mutate()` or\n`mutateAsync()` explicitly.\n\n```jsx\nconst mutation = useMutation({\n  mutationFn: (newTodo) =>\n    fetch('\u002Fapi\u002Ftodos', {\n      method: 'POST',\n      body: JSON.stringify(newTodo),\n    }).then(r => r.json()),\n\n  onSuccess: (data, variables, context) => {\n    \u002F\u002F data = server response, variables = what you passed to mutate()\n    queryClient.invalidateQueries({ queryKey: ['todos'] })\n  },\n  onError: (error, variables, context) => {\n    console.error('Mutation failed:', error.message)\n  },\n  onSettled: () => {\n    \u002F\u002F runs after onSuccess OR onError — good for cleanup\n  },\n})\n\n\u002F\u002F trigger it\nmutation.mutate({ title: 'Buy milk' })\n```\n\n`mutation.status` cycles through `idle → pending → success | error`.\nUse `mutation.isPending` to disable the submit button.\n\n**Rule of thumb:** always invalidate or update the relevant query cache\nin `onSuccess` so the UI stays in sync with the server.\n",{"id":339,"difficulty":75,"q":340,"a":341},"optimistic-updates","How do you implement optimistic updates with useMutation?","**Optimistic updates** apply the expected change to the cache immediately\n— before the server responds — so the UI feels instant. If the mutation\nfails you roll back using the snapshot saved in `onMutate`.\n\n```jsx\nconst queryClient = useQueryClient()\n\nconst toggleTodo = useMutation({\n  mutationFn: (todo) =>\n    fetch(`\u002Fapi\u002Ftodos\u002F${todo.id}`, { method: 'PATCH',\n      body: JSON.stringify({ done: !todo.done }) }).then(r => r.json()),\n\n  onMutate: async (todo) => {\n    \u002F\u002F 1. cancel any in-flight refetch (avoids overwriting our optimistic data)\n    await queryClient.cancelQueries({ queryKey: ['todos'] })\n\n    \u002F\u002F 2. snapshot the previous value\n    const previous = queryClient.getQueryData(['todos'])\n\n    \u002F\u002F 3. optimistically update the cache\n    queryClient.setQueryData(['todos'], (old) =>\n      old.map(t => t.id === todo.id ? { ...t, done: !t.done } : t)\n    )\n\n    return { previous }  \u002F\u002F returned as \"context\"\n  },\n\n  onError: (_err, _todo, context) => {\n    \u002F\u002F 4. roll back on failure\n    queryClient.setQueryData(['todos'], context.previous)\n  },\n\n  onSettled: () => {\n    \u002F\u002F 5. always refetch to sync with server truth\n    queryClient.invalidateQueries({ queryKey: ['todos'] })\n  },\n})\n```\n\nThe pattern — cancel → snapshot → optimistic write → rollback on error →\ninvalidate on settle — is the canonical React Query optimistic workflow.\n\n**Rule of thumb:** use optimistic updates for high-frequency interactions\n(toggles, likes, reorder) where a 200–500 ms spinner would feel sluggish.\n",{"id":343,"difficulty":34,"q":344,"a":345},"query-invalidation","How does query invalidation work and why is it preferred over manual cache writes?","**Query invalidation** marks a cached query as stale so that React Query\nrefetches it the next time an active observer is present (or immediately\nif one is currently mounted).\n\n```jsx\n\u002F\u002F After creating a new post, invalidate the list\nconst mutation = useMutation({\n  mutationFn: createPost,\n  onSuccess: () => {\n    \u002F\u002F exact: false (default) — invalidates 'posts' and all sub-keys\n    queryClient.invalidateQueries({ queryKey: ['posts'] })\n\n    \u002F\u002F exact: true — invalidates only this exact key\n    queryClient.invalidateQueries({ queryKey: ['posts', 'list'], exact: true })\n  },\n})\n```\n\nInvalidation is preferred over `setQueryData` for list-after-create\nbecause the server may add timestamps, IDs, or derived fields that the\nclient can't predict. Re-fetching gets you the canonical truth.\n\n**Rule of thumb:** invalidate for create\u002Fdelete operations (server adds\ndata you don't have); use `setQueryData` only for update operations where\nyou already have the complete new object.\n",{"id":347,"difficulty":34,"q":348,"a":349},"dependent-queries","How do you fetch query B only after query A has completed (dependent queries)?","Use the **`enabled`** option. When `enabled` is `false` the query is\nsuspended — it will not fetch until the expression becomes truthy.\n\n```jsx\nfunction UserOrders({ userId }) {\n  \u002F\u002F Step 1: fetch the user\n  const { data: user } = useQuery({\n    queryKey: ['user', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  \u002F\u002F Step 2: fetch orders only once we have user.accountId\n  const { data: orders } = useQuery({\n    queryKey: ['orders', user?.accountId],\n    queryFn: () => fetchOrders(user.accountId),\n    enabled: !!user?.accountId,  \u002F\u002F waits for accountId to exist\n  })\n\n  return \u003COrderList orders={orders} \u002F>\n}\n```\n\n`enabled` accepts any expression — `!!id`, `isSuccess`, a feature flag\n— and React Query will start the query the moment the value becomes\ntruthy.\n\n**Rule of thumb:** always guard with `!!value` not just `value`, so that\nempty strings and `0` don't accidentally enable a query.\n",{"id":351,"difficulty":75,"q":352,"a":353},"infinite-queries","How does useInfiniteQuery work for paginated or infinite-scroll data?","**`useInfiniteQuery`** manages a list of pages. Each page is fetched via\n`queryFn` which receives `pageParam` (the cursor\u002Fpage number for that\npage). `getNextPageParam` derives the next cursor from the last page.\n\n```jsx\nconst {\n  data,           \u002F\u002F { pages: [...], pageParams: [...] }\n  fetchNextPage,\n  hasNextPage,\n  isFetchingNextPage,\n} = useInfiniteQuery({\n  queryKey: ['posts'],\n  queryFn: ({ pageParam = 0 }) =>\n    fetch(`\u002Fapi\u002Fposts?cursor=${pageParam}`).then(r => r.json()),\n\n  getNextPageParam: (lastPage) =>\n    lastPage.nextCursor ?? undefined, \u002F\u002F undefined stops pagination\n\n  initialPageParam: 0,               \u002F\u002F v5 required option\n})\n\n\u002F\u002F Flatten all pages for rendering\nconst posts = data?.pages.flatMap(p => p.items) ?? []\n```\n\nCall `fetchNextPage()` from an \"Load more\" button or an intersection\nobserver at the bottom of the list.\n\n**Rule of thumb:** `useInfiniteQuery` is for cursor-based or\noffset-based lists; for discrete page navigation (page 1 \u002F 2 \u002F 3\nbuttons) use a plain `useQuery` with the page number in the key.\n",{"id":355,"difficulty":42,"q":356,"a":357},"queryclient-setup","How do you set up QueryClient and QueryClientProvider?","Create a **`QueryClient`** instance once at app startup and wrap the\ntree with **`QueryClientProvider`**. Every hook inside the tree shares\nthat client.\n\n```jsx\n\u002F\u002F main.tsx \u002F _app.tsx\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query'\nimport { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools'\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60,      \u002F\u002F global 1-min freshness\n      retry: 2,                   \u002F\u002F retry failed queries twice\n    },\n  },\n})\n\nexport default function App() {\n  return (\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CRouter \u002F>\n      \u003CReactQueryDevtools initialIsOpen={false} \u002F> {\u002F* dev-only panel *\u002F}\n    \u003C\u002FQueryClientProvider>\n  )\n}\n```\n\n`QueryClient` is created outside the component so it is not re-created\non every render. In tests, create a fresh `QueryClient` per test to\nprevent shared state between cases.\n\n**Rule of thumb:** never create the `QueryClient` inside a component —\nit would reset the cache on every render.\n",{"id":359,"difficulty":34,"q":360,"a":361},"prefetching","What is prefetchQuery and when would you use it?","**`prefetchQuery`** populates the cache proactively — before the\ncomponent that needs the data has mounted. This eliminates the loading\nstate for routes the user is likely to visit next.\n\n```jsx\nconst queryClient = useQueryClient()\n\n\u002F\u002F On mouse-enter of a \"User Profile\" link\nfunction UserLink({ userId }) {\n  const handleMouseEnter = () => {\n    queryClient.prefetchQuery({\n      queryKey: ['user', userId],\n      queryFn: () => fetchUser(userId),\n      staleTime: 1000 * 60 * 5,  \u002F\u002F don't prefetch if cached and fresh\n    })\n  }\n  return \u003CLink to={`\u002Fusers\u002F${userId}`} onMouseEnter={handleMouseEnter}>...\u003C\u002FLink>\n}\n```\n\nYou can also call `prefetchQuery` in a route loader (React Router v6+,\nNext.js `getServerSideProps`, TanStack Router) to fetch data on the\nserver before the page renders.\n\n**Rule of thumb:** prefetch on hover\u002Fintent; do not prefetch every\npossible route eagerly or you'll waste bandwidth.\n",{"id":363,"difficulty":34,"q":364,"a":365},"server-vs-client-state","What is the difference between server state and client state, and why does it matter?","**Server state** is data that lives on a remote server and is fetched\nasynchronously — it can change outside your app (another user edits it,\na cron job updates it). **Client state** is ephemeral UI state that only\nyour app owns: modal open\u002Fclosed, form draft, selected tab.\n\n```jsx\n\u002F\u002F Client state — lives only in the browser\nconst [isModalOpen, setIsModalOpen] = useState(false)\n\n\u002F\u002F Server state — a snapshot of remote data\nconst { data: todos } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n})\n\u002F\u002F \"todos\" can become stale while the user is on the page\n```\n\nServer state has extra concerns client state doesn't: caching, staleness,\nbackground synchronisation, deduplication, pagination. Managing it with\n`useState` forces you to re-implement all of these manually.\n\n**Rule of thumb:** use `useState`\u002F`useReducer`\u002FZustand for client state;\nuse React Query (or SWR) for server state — keep the two layers separate.\n",{"id":367,"difficulty":34,"q":368,"a":369},"pagination-patterns","How do you implement paginated data fetching with useQuery?","Include the **page number** in the query key so each page has its own\ncache entry. Use **`placeholderData: keepPreviousData`** (v5) to avoid\nblanking the UI between pages.\n\n```jsx\nimport { useQuery, keepPreviousData } from '@tanstack\u002Freact-query'\n\nfunction TodoList() {\n  const [page, setPage] = useState(1)\n\n  const { data, isPlaceholderData } = useQuery({\n    queryKey: ['todos', page],\n    queryFn: () => fetchTodos(page),\n    placeholderData: keepPreviousData, \u002F\u002F keep showing page N while N+1 loads\n  })\n\n  return (\n    \u003C>\n      {isPlaceholderData && \u003CLoadingBar \u002F>}\n      \u003CList items={data?.items} \u002F>\n      \u003Cbutton\n        onClick={() => setPage(p => p - 1)}\n        disabled={page === 1}>Prev\u003C\u002Fbutton>\n      \u003Cbutton\n        onClick={() => setPage(p => p + 1)}\n        disabled={!data?.hasMore}>Next\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n```\n\nReact Query can also prefetch the next page when `hasMore` is true,\nmaking forward navigation feel instant.\n\n**Rule of thumb:** always put pagination state in the query key; never\nmutate an external variable that the key doesn't reflect.\n",{"id":371,"difficulty":34,"q":372,"a":373},"react-query-vs-redux","When should you use React Query instead of Redux for data fetching?","Use **React Query** when data originates from a server and your main\nconcerns are caching, staleness, and background sync. Use **Redux** when\nyou have complex cross-cutting client state (undo\u002Fredo, multi-step\nworkflows, real-time collaborative state shared between many components).\n\n```jsx\n\u002F\u002F React Query — server data, auto-caching, zero boilerplate\nconst { data: user } = useQuery({ queryKey: ['user'], queryFn: getUser })\n\n\u002F\u002F Redux (RTK Query) — same fetching capability, but adds global client state\nconst user = useSelector(selectUser)       \u002F\u002F cross-slice derivations\ndispatch(userSlice.actions.setRole('admin')) \u002F\u002F synchronous client mutation\n```\n\nMany teams use both: React Query for server state, Zustand or Redux for\nthe small amount of true client state (auth session, UI preferences).\nRTK Query (Redux Toolkit's built-in fetching solution) is an alternative\nif you're already invested in Redux.\n\n**Rule of thumb:** reach for React Query first; only add Redux if you\ngenuinely need global synchronous state across many parts of the app.\n",{"id":375,"difficulty":34,"q":376,"a":377},"react-query-vs-swr","How does React Query compare to SWR?","Both **SWR** (Vercel) and **React Query** implement stale-while-revalidate\ncaching. The differences are in scope and power:\n\n```jsx\n\u002F\u002F SWR — minimal, opinionated API\nconst { data, error, isLoading } = useSWR('\u002Fapi\u002Fuser', fetcher)\n\n\u002F\u002F React Query — richer API: mutations, infinite queries, optimistic updates\nconst { data, isLoading } = useQuery({ queryKey: ['\u002Fapi\u002Fuser'], queryFn: fetcher })\nconst mutation = useMutation({ mutationFn: updateUser })\n```\n\nSWR is simpler — almost no configuration, tiny bundle. React Query has\nmore features: `useMutation` with full lifecycle callbacks, `useInfiniteQuery`,\n`QueryClient` for server-side prefetching, advanced `select` transforms,\nand the excellent DevTools panel.\n\n**Rule of thumb:** SWR for small apps that only need fetch-and-cache;\nReact Query when you also need mutations, optimistic updates, pagination,\nor fine-grained cache control.\n",{"id":379,"difficulty":75,"q":380,"a":381},"suspense-mode","How does React Query integrate with React Suspense?","Pass **`useSuspenseQuery`** (v5) instead of `useQuery` to let React\nSuspense handle the loading state declaratively. The component suspends\nwhile data loads; wrap it in `\u003CSuspense>` with a fallback.\n\n```jsx\n\u002F\u002F v5 API\nimport { useSuspenseQuery } from '@tanstack\u002Freact-query'\n\nfunction UserProfile({ userId }) {\n  \u002F\u002F throws a Promise while loading → Suspense catches it\n  const { data } = useSuspenseQuery({\n    queryKey: ['user', userId],\n    queryFn: () => fetchUser(userId),\n  })\n  \u002F\u002F data is ALWAYS defined here — no isLoading check needed\n  return \u003Ch1>{data.name}\u003C\u002Fh1>\n}\n\n\u002F\u002F Parent\nfunction Page() {\n  return (\n    \u003CErrorBoundary fallback={\u003CErrorPage \u002F>}>\n      \u003CSuspense fallback={\u003CSkeleton \u002F>}>\n        \u003CUserProfile userId={1} \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002FErrorBoundary>\n  )\n}\n```\n\nError states bubble to the nearest `\u003CErrorBoundary>` instead of being\nreturned as a flag. This moves loading and error handling out of the\ndata component and into the layout layer.\n\n**Rule of thumb:** use Suspense mode when you want co-located data\nfetching with parent-controlled loading UI; keep the imperative\n`isLoading` approach in forms and components that handle their own\nerror display.\n",{"id":383,"difficulty":75,"q":384,"a":385},"ssr-hydration","How do you use React Query with SSR (e.g., Next.js) and avoid hydration mismatches?","Use **`HydrationBoundary`** with `dehydrate` to pass the server-fetched\ncache to the client, avoiding a client-side refetch on first render.\n\n```jsx\n\u002F\u002F app\u002Fusers\u002Fpage.tsx (Next.js App Router)\nimport { dehydrate, HydrationBoundary, QueryClient }\n  from '@tanstack\u002Freact-query'\n\nexport default async function UsersPage() {\n  const queryClient = new QueryClient()\n\n  \u002F\u002F prefetch on the server\n  await queryClient.prefetchQuery({\n    queryKey: ['users'],\n    queryFn: fetchUsers,\n  })\n\n  return (\n    \u002F\u002F serialise the cache and send it to the client\n    \u003CHydrationBoundary state={dehydrate(queryClient)}>\n      \u003CUserList \u002F>   {\u002F* useQuery(['users']) hits the cache immediately *\u002F}\n    \u003C\u002FHydrationBoundary>\n  )\n}\n```\n\nOn the client, React Query reads the dehydrated state and populates its\ncache before components mount, so they render with data immediately and\nno hydration mismatch occurs.\n\n**Rule of thumb:** always create a fresh `QueryClient` per request on the\nserver — never share the same instance across requests or you'll leak data\nbetween users.\n",{"id":387,"difficulty":34,"q":388,"a":389},"polling-refetch-interval","How do you poll an endpoint on a fixed interval with React Query?","Set **`refetchInterval`** to a millisecond value. React Query will refetch\nthe query on that interval while the component is mounted. Set\n`refetchIntervalInBackground: true` to keep polling even when the tab is\nnot focused.\n\n```jsx\nconst { data: jobStatus } = useQuery({\n  queryKey: ['job', jobId],\n  queryFn: () => fetchJobStatus(jobId),\n  refetchInterval: 3000,               \u002F\u002F poll every 3 seconds\n  refetchIntervalInBackground: false,  \u002F\u002F pause when tab is hidden (default)\n})\n\n\u002F\u002F Dynamic interval — stop polling once the job is done\nconst { data } = useQuery({\n  queryKey: ['job', jobId],\n  queryFn: () => fetchJobStatus(jobId),\n  refetchInterval: (query) => {\n    \u002F\u002F return false to stop; return ms to continue\n    return query.state.data?.status === 'done' ? false : 2000\n  },\n})\n```\n\nThe function form of `refetchInterval` receives the current query object,\nletting you implement \"poll until done\" patterns without extra state.\n\n**Rule of thumb:** always use the function form when you want to stop\npolling on a condition — a fixed interval that never stops wastes network\non completed resources.\n",20,{"description":32},"React Query interview questions — useQuery, useMutation, query keys, caching, stale time, refetching, optimistic updates, and async state management patterns.","react\u002Fstate-management\u002Fasync-state-react-query","Async State & React Query","iVoG3RXlvfrXcn1S9vuDDXabHtP3hZ0NtPwLeckGYTA",1782244096693]