In a React Native sneaker store, why can a cart kept in one React Context make low-end Android phones stutter on every quantity change?
answer
- every consumer re-renders on a new value
- hidden screens are still mounted
- renders run on the single JS thread
- a busy JS thread drops frames
- subscribe narrowly with an external store
basics
~20 sEvery component reading the cart Context re-renders when its value changes, including those on mounted but hidden screens. That work runs on React Native's single JavaScript thread, so a slow phone misses frames and taps lag.
solid answer
~50 sContext has no selectors: when the provider's value changes, every component that reads it re-renders, whether it shows the cart count or only needed the add function. In a sneaker store the cart is read by the tab bar badge, product screens, the cart tab and often screens still mounted underneath in a stack or in other tabs. All of that render work runs on the JavaScript thread, which also processes touches and JavaScript-driven animation. A 60 Hz frame is about 16 ms; a low-end Android CPU can spend several frames re-rendering, so `Pressable` feedback and JS-driven animations freeze while native scrolling carries on. The fix is to keep fast-changing data like the cart out of one broad Context: use an external store (Zustand, Redux Toolkit or Jotai) where each component subscribes to the slice it shows, and keep Context for rarely changing values such as theme or the user.
go deeper
Recall that every component reading a Context re-renders when its value changes, and that React Native renders on one JavaScript thread.
Explain the fan-out, including mounted hidden screens, and why a long render delays taps and JS animations but not native scrolling.
Show how you would decide what goes in Context versus a store, and how you would verify the stutter on a low-end device in a release build.
Set a team rule for where fast-changing shared state lives, and a performance floor defined by the slowest supported Android device.
## What Context does when the value changes A React Context carries one value from a provider to any descendant that reads it with `use` or `useContext`. It has no notion of *which part* of that value a component cares about. When the provider renders with a new value, React re-renders **every consumer** of that Context. For a cart object that is replaced on every change, that means: - the tab bar badge that shows the item count re-renders; - every product card that reads the cart only to call `addToCart` re-renders; - the cart screen re-renders, which is the one component that actually needs to; - consumers on screens that are open but not visible, such as an earlier screen in a stack or a tab the user visited before, typically re-render too, because they are still mounted. On the web this is often invisible. On a phone it is where the cost lands. ## Why a phone feels it: the JavaScript thread React Native runs your React code on a **single JavaScript thread**. The React Native performance docs describe what that thread does: business logic, API calls, **touch event processing** and the React render work whose results are committed to native views. If that thread is busy for longer than a frame, the frame is dropped. | Work | Runs on | Effect of a long re-render | |---|---|---| | React renders and reconciliation | JavaScript thread | delayed | | Touch handling for `Pressable` and gesture callbacks in JS | JavaScript thread | taps feel late | | `Animated` without the native driver | JavaScript thread | animation freezes | | `ScrollView` scrolling itself | main (UI) thread | keeps moving | The docs give the canonical example: a state change at the root that re-renders expensive subtrees can take around 200 ms, which is about 12 dropped frames. A flagship phone hides a lot of that; a low-end Android device, with a slower CPU, does not. That is why the complaint arrives as "the add button on cheap phones feels sticky" rather than as a visible bug. Two measurement notes: dev builds run the JavaScript thread much slower than release builds, so judge the stutter in a release build; and count consumers before optimising individual components, because the fan-out is the cost. ## What to do instead 1. **Move fast-changing shared data to an external store.** Zustand, Redux Toolkit and Jotai let a component subscribe to a **selected slice** and re-render only when that slice changes: - the badge subscribes to the item count, a number; - a product card subscribes to whether *its* sneaker is in the cart, a boolean; - components that only dispatch or call an action subscribe to nothing that changes. 2. **If you keep Context, narrow it.** Split state from actions so action-only consumers stop re-rendering, memoise the provider value, and give unrelated data separate providers. This works but is manual; every new consumer is another place to get it wrong. 3. **Keep Context for what rarely changes.** Theme, locale and the signed-in user change a few times per session; their fan-out is harmless. ## A rule of thumb for the sneaker store - Cart contents and quantities change on every tap: external store with selectors. - Server data such as the product catalogue belongs in a server-state cache, not a client store. - Theme and user: Context is fine. The underlying question interviewers probe is not "Context bad, library good" but whether you know **what a change costs on the device**: how many components re-render, and whether that work fits in a frame on the slowest phone you support.
- Why does the product list keep scrolling smoothly while the add-to-cart button feels stuck?Scrolling a `ScrollView` or `FlatList` is driven by the native main thread, so it continues even when the JavaScript thread is busy; the scroll events reach JavaScript late, but the scroll itself does not wait. Pressing a button needs the JavaScript thread to process the touch and re-render the feedback, so a long re-render delays it.
- When is a cart Context still acceptable in a React Native app?When few components read it and they are cheap: a small app whose badge and cart screen are the only consumers will not notice. It stops being acceptable when many mounted components read it, the value changes on every tap, and the app must stay responsive on low-end Android. At that point narrow subscriptions pay for themselves.
One loudspeaker announcing every basket change to the whole shop, so every assistant stops work to listen, versus each assistant wearing an earpiece tuned only to the aisle they look after.
saying these in an interview costs you the question
- Only components on the visible screen re-render when a Context value changes
- React renders run on the UI thread, so a long render only affects that screen
- Memoising each product card stops it re-rendering when the cart Context changes
- If it is smooth in the dev build on a flagship phone, it will be smooth everywhere
- Context has selectors, so each consumer re-renders only for the fields it reads