Blogged Answers: React, Redux, and Context Behavior
This is a post in theBlogged Answers series.
There's a couple assumptions that I've seen pop up repeatedly:
- React-Redux is "just a wrapper around React context"
- You can avoid re-renders caused by React context if you destructure the context value
Both of these assumptions are incorrect, and I want to clarify how they actually work so that you can avoid mis-using them in the future.
For context behavior, say we have this initial setup:
function ProviderComponent() { const [contextValue, setContextValue] = useState({a: 1, b: 2}); return ( <MyContext.Provider value={contextValue}> <SomeLargeComponentTree /> </MyContext.Provider> )}function ChildComponent() { const {a} = useContext(MyContext); return <div>{a}</div>}If theProviderComponent were to then callsetContextValue({a: 1, b: 3}),theChildComponentwould re-render, even though it only cares about thea field based on destructuring. It also doesn't matter how many levels of hooks are wrapping thatuseContext(MyContext) call. A new reference was passed into the provider, soall consumers will re-render. In fact, if I were to explicitly re-render with<MyContext.Provider value={{a: 1, b: 2}}>,ChildComponent wouldstill re-render because a new object reference has been passed into the provider! (Note that this is why you should never pass object literals directly into context providers, but rather either keep the data in state or memoize the creation of the context value.)
For React-Redux: yes, ituses context internally,but only to pass the Redux storeinstance down to child components - it doesn't passthe store state using context!. If you look at the actual implementation, it's roughly this but with more complexity:
function useSelector(selector) { const [, forceRender] = useReducer( counter => counter + 1, 0); const {store} = useContext(ReactReduxContext); const selectedValueRef = useRef(selector(store.getState())); useLayoutEffect(() => { const unsubscribe = store.subscribe(() => { const storeState = store.getState(); const latestSelectedValue = selector(storeState); if(latestSelectedValue !== selectedValueRef.current) { selectedValueRef.current = latestSelectedValue; forceRender(); } }) return unsubscribe; }, [store]) return selectedValueRef.current;}So,React-Redux only uses context to pass the store itself down, and then usesstore.subscribe() to be notified when the store state has changed. This results in very different performance behavior than using context to pass data.
There was an extensive discussion of context behavior inReact issue #14110: Provide more ways to bail out of hooks. In that thread,Sebastian Markbage specifically said:
My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It's also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It's not ready to be used as a replacement for all Flux-like state propagation.
In fact, wedid try to pass the store state in context in React-Redux v6, and it turned out to be insufficiently performant for our needs,which is why we had to rewrite the internal implementation to use direct subscriptions again in React-Redux v7.
For complete detail on how React-Redux actually works, read my postThe History and Implementation of React-Redux, which covers the changes to the internal implementation over time, and how we actually use context.
This is a post in theBlogged Answers series.Other posts in this series:
- Aug 08, 2023 -Blogged Answers: My Experience Modernizing Packages to ESM
- Jul 06, 2022 -Blogged Answers: How I Estimate NPM Package Market Share (and how Redux usage compares to other libraries)
- Jun 22, 2021 -Blogged Answers: The Evolution of Redux Testing Approaches
- Jan 18, 2021 -Blogged Answers: Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)
- Jun 21, 2020 -Blogged Answers: React Components, Reusability, and Abstraction
- May 17, 2020 -Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
- May 12, 2020 -Blogged Answers: Why I Write
- Feb 22, 2020 -Blogged Answers: Why Redux Toolkit Uses Thunks for Async Logic
- Feb 22, 2020 -Blogged Answers: Coder vs Tech Lead - Balancing Roles
- Jan 19, 2020 -Blogged Answers: React, Redux, and Context Behavior
- Jan 01, 2020 -Blogged Answers: Years in Review, 2018-2019
- Jan 01, 2020 -Blogged Answers: Reasons to Use Thunks
- Jan 01, 2020 -Blogged Answers: A Comparison of Redux Batching Techniques
- Nov 26, 2019 -Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer
- Jul 10, 2019 -Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
- Jan 19, 2019 -Blogged Answers: Debugging Tips
- Mar 29, 2018 -Blogged Answers: Redux - Not Dead Yet!
- Dec 18, 2017 -Blogged Answers: Resources for Learning Redux
- Dec 18, 2017 -Blogged Answers: Resources for Learning React
- Aug 02, 2017 -Blogged Answers: Webpack HMR vs React-Hot-Loader
- Sep 14, 2016 -How I Got Here: My Journey Into the World of Redux and Open Source