Advanced React & Redux
Refs, context, memoization, HOC/render-props patterns, and the Redux data flow, middleware, normalization, and selectors.
12 questions
JuniorTheoryVery commonIn Redux, what are the store, actions, reducers, and dispatch?
In Redux, what are the store, actions, reducers, and dispatch?
The store holds the whole application state as one read-only object tree. An action is a plain object describing what happened, with a type field. A reducer is a pure function (state, action) => newState that returns the next state without mutating the old one.
Common mistakes
- ✗Mutating the state object inside a reducer instead of returning a new one
- ✗Changing the store without dispatching an action
- ✗Putting side effects or async calls inside a reducer
Follow-up questions
- →Why must a reducer be a pure function that returns a new state?
- →Why is
dispatchthe only sanctioned way to change the store?
MiddleTheoryVery commonHow do Component, PureComponent, and React.memo differ on re-rendering?
How do Component, PureComponent, and React.memo differ on re-rendering?
Component always re-renders when its parent does. PureComponent implements shouldComponentUpdate with a shallow compare of props and state, skipping the render when nothing at the top level changed. A mutated nested object or a new inline prop defeats all three.
Common mistakes
- ✗Mutating a nested prop object in place, which a shallow compare cannot detect
- ✗Thinking
React.memoandPureComponentdo a deep compare - ✗Passing a new inline object/array/function prop each render, breaking the bail-out
Follow-up questions
- →Why can mutating a nested object silently break a
PureComponentbail-out? - →Does
React.memoskip a re-render caused by an internaluseStatechange?
JuniorTheoryCommonWhat problem does React Context solve?
What problem does React Context solve?
Context lets a value reach deeply nested components without passing it as a prop through every level — it removes prop drilling. A provider sets a value high in the tree, and any descendant reads it directly with useContext, however many layers sit between.
Common mistakes
- ✗Using context for data that only a couple of nearby components need
- ✗Thinking context replaces a state manager rather than passing existing data
- ✗Believing context flows data upward from child to parent
Follow-up questions
- →What kinds of data are a good fit for context versus props?
- →Why does context not flow data from a child up to a parent?
JuniorTheoryCommonWhat are refs in React, and what are they commonly used for?
What are refs in React, and what are they commonly used for?
A ref is an escape hatch that gives imperative access to a value that survives re-renders without triggering one. Attached to a host element it holds the underlying DOM node — used to focus an input, measure layout, or play media; attached to a class component it holds the instance.
Common mistakes
- ✗Reaching for a ref to do what state and props already handle declaratively
- ✗Expecting a
ref.currentmutation to trigger a re-render - ✗Putting a ref on a function component without
forwardRef
Follow-up questions
- →Why does mutating
ref.currentdeliberately avoid triggering a re-render? - →Why does a function component need
forwardRefto accept a ref?
MiddleTheoryCommonCompare the React reuse patterns higher-order components and render props
Compare the React reuse patterns higher-order components and render props
Both share cross-cutting logic. A higher-order component is a function taking a component and returning a wrapped one with extra props/behavior (e.g. connect), which deepens the tree and can collide on prop names. Render props pass a function as a prop that the component calls to render, making data flow explicit and slot-like but causing wrapper nesting. Custom hooks largely supersede both for logic reuse today.
Common mistakes
- ✗Declaring a HOC inside render, which remounts the wrapped component every render
- ✗Thinking HOCs and render props can share stateful logic but hooks cannot
- ✗Ignoring prop-name collisions a HOC can introduce on the wrapped component
Follow-up questions
- →Why can declaring a HOC inside the render method cause remounts?
- →How does a custom hook reuse stateful logic without adding tree nesting?
MiddleTheoryCommonDescribe the unidirectional data flow in the state container Redux
Describe the unidirectional data flow in the state container Redux
Data flows one way. The UI calls store.dispatch(action). The store runs the reducers, pure functions that take the current state and the action and return either the same state or a new state immutably — never mutating the old one. The store saves the returned state and notifies subscribers, which re-read it and re-render. State is read-only; the only way to change it is by dispatching an action.
Common mistakes
- ✗Mutating state inside a reducer instead of returning a new immutable object
- ✗Putting side effects or async calls directly in a reducer instead of middleware
- ✗Thinking dispatch optional — it is the only way to change store state
Follow-up questions
- →Why must reducers be pure and avoid mutating the previous state?
- →Where do async side effects like data fetching belong in this flow?
MiddleTheoryOccasionalWhat is Redux middleware for, and where does it sit in the flow?
What is Redux middleware for, and where does it sit in the flow?
Middleware is a layer that intercepts every dispatched action after dispatch but before it reaches the reducers. It is where side effects live: logging, async calls, analytics, crash reporting. Each middleware can inspect the action, transform it, delay it, dispatch others, or stop it.
Common mistakes
- ✗Thinking middleware runs inside or after the reducers rather than before them
- ✗Forgetting to call
next(action), silently swallowing the action - ✗Believing only one middleware can be active rather than a composed chain
Follow-up questions
- →What happens to an action if a middleware never calls
next? - →Why do async libraries like thunk and saga live in middleware?
MiddleTheoryOccasionalWhat is state normalization in Redux, and why do it?
What is state normalization in Redux, and why do it?
Normalization flattens nested data into a database-like shape: each entity type lives in a lookup object keyed by id, and relationships are stored as arrays of ids, not embedded copies. This removes duplication and makes updates cheap, local, and O(1) to look up.
Common mistakes
- ✗Storing the same record nested in many places, so an update must touch them all
- ✗Confusing normalization with serialization or compression
- ✗Keeping deep API-shaped trees in the store and fighting update churn
Follow-up questions
- →How does keying entities by id make an update O(1) and local?
- →When is normalization overkill for a small piece of state?
MiddleTheoryOccasionalWhat makes a React component re-render, and how does React.memo change that?
What makes a React component re-render, and how does React.memo change that?
A component re-renders when its own state changes, when its parent re-renders, or when a context it consumes changes. React does NOT deep-compare props by default — a parent render re-renders all children regardless of whether their props changed. React.memo (and PureComponent/shouldComponentUpdate for classes) wraps a component to bail out of re-render when a shallow prop comparison shows no change.
Common mistakes
- ✗Assuming React skips children whose props are unchanged without a memo wrapper
- ✗Passing a new inline object/array/function prop each render, defeating
React.memo - ✗Thinking
React.memodoes a deep compare rather than a shallow one
Follow-up questions
- →Why does passing a new inline arrow as a prop break a
React.memochild? - →How does
useCallbackhelp keep a memoized child from re-rendering?
SeniorTheoryOccasionalIn Redux, how do the async libraries redux-thunk and redux-saga compare?
In Redux, how do the async libraries redux-thunk and redux-saga compare?
Both are middleware for handling async work and side effects, since reducers must stay pure. redux-thunk lets an action creator return a function that receives dispatch/getState — minimal, easy to learn, ideal for simple async flows.
Common mistakes
- ✗Thinking thunk uses generators or saga returns plain functions, swapping the two
- ✗Reaching for saga's complexity on a project with only simple async flows
- ✗Believing either lets reducers perform async work directly
Follow-up questions
- →Why are saga's declarative effects easier to unit-test than thunk's imperative calls?
- →Which async flows justify saga's added complexity over thunk?
MiddleTheoryRareWhen should you reach for React Context, and when should you avoid it?
When should you reach for React Context, and when should you avoid it?
Use context for genuinely global, low-frequency data many components read — user, theme, locale. Avoid it for data that changes often or that only a few nearby components need: every consumer re-renders when the provider value identity changes.
Common mistakes
- ✗Putting fast-changing data in context and re-rendering large subtrees
- ✗Passing a new object literal as the provider value every render
- ✗Using context where simple props or composition would do
Follow-up questions
- →Why does passing a new object literal as the provider value hurt performance?
- →How does splitting one context into several limit re-render scope?
MiddleTheoryRareWhat is the selector library reselect, and why use memoized selectors?
What is the selector library reselect, and why use memoized selectors?
reselect creates memoized selectors that compute derived data from the Redux store. A selector caches its result and recomputes only when its specific input slices change (compared by reference), so expensive transforms (filtering, sorting, aggregating) run once instead of on every render.
Common mistakes
- ✗Recomputing derived data in the component on every render instead of in a selector
- ✗Returning a new array or object from a plain selector, defeating memoization
- ✗Thinking
reselectmemoizes actions rather than derived state
Follow-up questions
- →Why does a stable selector reference prevent a connected component re-render?
- →What breaks memoization if a selector builds a new object each call?