My understanding is that React's Context API was essentially introduced for quick and dirty global state management, particularly before Redux Toolkit was introduced to simplify the overhead of implementing Redux.
My understanding is that one of the main downsides of Context API is that it any update to any property on it will re-render all component fields which are bound to Context API (full re-render).
I recently explained that downside to someone and he asked why that wouldn't be the case with Redux. He said Redux uses the Context API under the covers which appears to be the case based on some googling:
https://react-redux.js.org/using-react-redux/accessing-store#:~:text=Internally%2C%20React%20Redux%20uses%20React's,object%20instance%20generated%20by%20React.
A few questions:
1 - Can you confirm that Context API does a full component re-render when any state value is changed on it?
2 - Can you confirm that Redux uses Context API under the covers? And can you confirm if Redux Toolkit still uses Context API under the covers?
3 - I was under the impression that Context API was introduced to be a simpler alternative to tools like Redux. But if Redux uses Context API then did Context API come first but maybe it wasn't exposed directly to the developer?
4 - If Redux does use Context API under the covers then can you provide any insight into how Redux avoids a full component re-render? I would assume that there's somevtype of mapping layer but I'd be interested to get some more insight into the specific implementation details
My understanding is that React's Context API was essentially introduced for quick and dirty global state management
That's a common misunderstanding. Context is not a state management system, any more than props is a state management system. Context (like props) is a way to get data from one component to another. The difference is that props always passes the data to direct children, while context makes the data available to whichever random components in a subtree are interested.
My understanding is that one of the main downsides of Context API is that it any update to any property on it will re-render all component fields which are bound to Context API (full re-render).
This is true. Similarly, if you change props, the component that receives those props must rerender
1 - Can you confirm that Context API does a full component re-render when any state value is changed on it?
Of the specific components that are listening to that context, yes.
2 - Can you confirm that Redux uses Context API under the covers? And can you confirm if Redux Toolkit still uses Context API under the covers?
React-redux does use context, yes. Pure redux and redux toolkit don't, since they're general purpose libraries not directly related to react, but I think you meant react-redux.
That <Provider store={store}> that you must render at the top of a react-redux app is there to provide a context to the components underneath it. Components that call hooks like useSelector or useDispatch then use the context to find the store that they should interact with.
3 - I was under the impression that Context API was introduced to be a simpler alternative to tools like Redux. But if Redux uses Context API then did Context API come first but maybe it wasn't exposed directly to the developer?
Context has existed for a long time, but it used to be an unofficial feature. They've also made it easier to use over time.
4 - If Redux does use Context API under the covers then can you provide any insight into how Redux avoids a full component re-render?
The context is only providing a minimal amount of things to the child components, most importantly the store object. The reference to the store object does not typically change, so since the context value does not change, the child components do not need to render. To see exactly what it's providing, see this code: https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.tsx#L33
The contents of the store does change, but that's not what the context is providing. To get the contents of the store, the individual components subscribe to the store, using redux's store.subscribe, plus a special hook called useSyncExternalStore. Basically, redux fires an event when the store's state is updated, and then the individual components set their own local state if it's a change they care about. This local state change is what causes the rerender.
If you're writing code that uses context, you're rarely going to be doing things fancy enough to require useSyncExternalStore or a custom subscription system. So the main things you'll want to keep in mind are:
Keep the context focused on a single task. For example, if you have a theme object to control your app's colors, and also a user object which describes who is currently logged in, put these in different contexts. That way a component that just cares about the theme doesn't need to rerender when the user changes, and vice versa.
If your context value is an object, memoize it so it's not changing on every render (see this documentation)
I'm a Redux maintainer. #NicholasTower gave a great answer, but to give some more details:
Context and Redux are very different tools that solve different problems, with some overlap.
Context is not a "state management" tool. It's a Dependency Injection mechanism, whose only purpose is to make a single value accessible to a nested tree of React components. It's up to you to decide what that value is, and how it's created. Typically, that's done using data from React component state, ie, useState and useReducer. So, you're actually doing all the "state management" yourself - Context just gives you a way to pass it down the tree.
Redux is a library and a pattern for separating your state update logic from the rest of your app, and making it easy to trace when/where/why/how your state has changed. It also gives your whole app the ability to access any piece of state in any component.
In addition, there are some distinct differences between how Context and (React-)Redux pass along updates. Context has some major perf limitations - in particular, any component that consumes a context will be forced to re-render, even if it only cares about part of the context value.
Context is a great tool by itself, and I use it frequently in my own apps. But, Context doesn't "replace Redux". Sure, you can use both of them to pass data down, but they're not the same thing. It's like asking "Can I replace a hammer with a screwdriver?". No, they're different tools, and you use them to solve different problems.
Because this is such a common question, I wrote an extensive post detailing the differences:
Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)
To answer your questions specifically:
Yes, updating a Context value forces all components consuming that context to re-render... but there's actually a good chance that they would be re-rendering anyway because React renders recursively by default, and setting state in a parent component causes all components inside of that parent to re-render unless you specifically try to avoid it. See my post A (Mostly) Complete Guide to React Rendering Behavior, which explains how all this works.
Yes, React-Redux does use Context internally... but only to pass down the Redux store instance, and not the current state value. This leads to very different update characteristics. Redux Toolkit, on the other hand, is just about the Redux logic and not related to any UI framework specifically.
Context was not introduced to be an alternative to Redux. There was a "legacy Context" API that existed in React well before Redux itself was created, and React-Redux used that up through v5. However, that legacy context API was broken in some key ways. The current React Context API was introduced in React 16.3 to fix the problems in legacy Context, not specifically to replace Redux.
React-Redux uses store subscriptions and selectors in each component instance, which is a completely different mechanism than how Context operates.
I'd definitely suggest reading the posts I linked above, as well as these other related posts:
Redux - Not Dead Yet!
When (and when not) to Reach for Redux
React, Redux, and Context Behavior.
Original blog post by Mark Erikson:
I'll just copy paste some info, but here's the original source and I recommend going directly here: https://blog.isquaredsoftware.com/2020/01/blogged-answers-react-redux-and-context-behavior/
More links here:
https://github.com/markerikson/react-redux-links
https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/
https://medium.com/async/how-useselector-can-trigger-an-update-only-when-we-want-it-to-a8d92306f559
An explanation of how React Context behaves, and how React-Redux uses Context internally
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 the ProviderComponent were to then call setContextValue({a: 1, b: 3}), the ChildComponent would re-render, even though it only cares about the a field based on destructuring. It also doesn't matter how many levels of hooks are wrapping that useContext(MyContext) call. A new reference was passed into the provider, so all consumers will re-render. In fact, if I were to explicitly re-render with <MyContext.Provider value={{a: 1, b: 2}}>, ChildComponent would still 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, it uses context internally, but only to pass the Redux store instance down to child components - it doesn't pass the 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 uses store.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 in React 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, we did 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 post The History and Implementation of React-Redux, which covers the changes to the internal implementation over time, and how we actually use context.
Related
I've recently started using react using functional components and react-query and it has been working fine except that I'm not clear about how to organize components properly.
the way I'm used to designing components is by having a top level component that performs all data access and passes data to it's child components through props. it also passes various callback handlers to child components so that when action is required, the top level component would update the data and passes the new data to child component. so in my case all calls to useQuery(), useMutation() reside in top level component but it's making the code extremely messy. but it's much like a page containing various child components that only display data or help user interact with data.
function Page(){
const [page, setPage] = useState(1)
const [size, setSize] = useState(10)
const persons = useQuery('persons', async ()=> await getPersons(page, size))
const addPerson = useMutation(async (args)=> {
const {id, name, desc} = args
await addPerson(id, name, description)
})
const person = useQuery('persons', async ()=> await getOnePerson(page, size), { enabled : false })
const addPersonCB = (id: number, name: string, desc: string)=> {
addPerson.mutate({id, name, desc})
}
// complex if/else logic to choose child components
the second approach is to disperse react useQuery() and useMutation throughout the components where it's need. and to further simplify things, if rendering logic is complex, each component would have a parent component that would perform the action and passes data as prop.
function PersonCard(props: PersonCardPropsType){
const {data, isLoading, isError, error} = useQuery(`personQuery${props.id}`, getPerson)
if(isLoading)
return <Wait />
if(isError)
return <Error reason={error} />
const record = data as PersonModel
return ( <PersonCardUI person={record} />)
}
and there are may compoenents for grid, form and etc each one in form of pair like
<PersonEditor />, <PersonEditorUI />, <PersonGrid />, <PersonGridUI />
in this case the calls are dispersed everywhere in the code. I want to know
For large projects, which approach is recommended and why?
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
It is generally considered a best practice to use useQuery where you need it. The separation into container / presentational components, while still possible, has been largely deprecated since hooks came around. With redux connect / mapStateToProps, it was best practice. Now, even in redux, you just call useSelector and useDispatch close to where you need it. This is no different in react-query.
There is a great talk on this subject from Mark Erikson: Hooks, HOCs and tradeoffs that I can totally recommend watching.
Using react-query hooks where they are needed not only avoids prop drilling, it also makes it easier for react-query to keep your data up-to-date, because more observers (=components that call useQuery) are mounting. This is also why it's best to just set a staleTime when you want to customize refetching behaviour. I've written about this in detail in React Query as a State Manager.
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Totally, as long as you don't sync server state to redux. page number and page size are considered "client state" because the client is control over that state. The user selects the page, and the server responds with the data depending on it. I also like to abstract that away together in custom hooks:
const useData = () => {
const pageNumber = useSelector(state => state.pageNumber)
return useQuery(["data", pageNumber], () => fetchData(pageNumber))
}
that way, you have a hook you can use wherever you want (without passing anything to it), and it re-fetches data automatically if the pageNumber changes.
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
If you don't need caching / loading states managed for you etc then sure. The only thing that comes to my mind where I don't want a query / mutation might be file downloads or so :)
Let’s step back for a minute and see what each of these abstractions help us achieve & then it makes it easier to see how one should architect an application.
Very broadly, you have
useQuery useSwr
Redux or any other global state management tool
concept of lift state up
Context api
Saving state in url (filters say or link to say a product/item page)
useQuery useSwr are responsible for managing remote state and provide a snapshot of your data that resides behind a remote API. They help with fetching data, caching, error handling, showing loading spinners. They give us additional features such as refetch after a certain interval or refetch on focus, refetch a certain # of times on error etc. Whether we then decide to call these individually in each component or a parent component is a matter of design i.e. implementation detail.
Redux and other global state management tool help with managing local state, globally throughout your client application. A great example of that would be your auth’ed user. That information probably is required globally so redux sounds like a great place to have that information. Shopping cart is another example that might make sense in a redux store.
Lift state up when you want to share information with siblings. This stackoverflow question is a perfect example of lifting state up. DataTableComponent now becomes a controlled component or what you might call a presentation component.
If lifting state up becomes too cumbersome then look at context api or perhaps redux.
So, taking shopping cart as an example, you might decide that context api makes better sense or perhaps lifting state up makes more sense rather than having it in a redux store. My point being that there isn't one way of doing this and it will be a judgement call.
Lastly, you might have a page with filters say, and you may want to give your users an ability to send a link/Url & you might want the recipients to see the same information as the sender. So, now you must save state in your url via say query strings.
Going back to my comment above, there is no one way of doing things. So, you may start off by lifting state but then realize it's too cumbersome so you may switch to context api or even redux.
But each of these abstractions usually do have a place in your application & I have used all the above abstractions in conjunction with each other quite successfully.
React has released the Context API and React Hooks but most of us are familiar with Redux, which one we should consider.
What is the purpose of using React Hooks and the React Context API? Please explain in detail how React Redux, Hooks and the Context API are different from each other.
React Context is used to store state and share it across multiple components. It is especially useful when you have a deep tree of components and do not want to pass down state as props across multiple levels of components. Contexts in React consist of a Provider (where you set the value of the context) and a Consumer (where you get the value).
React Hooks provide a useContext Hook, which is another way to access context values. useContext replaces the Consumer component.
Redux is a state management library. It does a lot more than simply storing state with a set/get interface as Contexts do. Internally, Redux actually uses React Context to store its state. However, it additionally forces you to change state via actions. This makes sense if your state changes are complex (e.g. a single action should change multiple parts of your state). In a complex application, Redux can prevent bugs and inconsistent state.
As a rule of thumb, you should use React Context as long as your state changes are simple. If you run into problems where it is hard to keep multiple parts of your state in sync, it might make sense to consider Redux.
Whether you want to use Context Consumers or Hooks is totally up to you and a matter of taste. The advantage of using Hooks is that you can consume multiple contexts more easily without having a deep component tree.
Context Consumers:
<AuthenticationContext.Consumer>
{user => (
<LanguageContext.Consumer>
{language => (
<StatusContext.Consumer>
{status => (
...
)}
</StatusContext.Consumer>
)}
</LanguageContext.Consumer>
)}
</AuthenticationContext.Consumer>
useContext Hooks:
const user = useContext(AuthenticationContext)
const language = useContext(LanguageContext)
const status = useContext(StatusContext)
As for the Providers, they are the same whether you use Hooks or not.
I know It may sound like a dumb question, But I am not able to get this solved in my head. Please bear with me.
In case when we use a state management system in React like Redux / Mob X, I guess the main purpose of these state management techniques is to provide a single source of Data and a more structured approach of updating it.
Say, I am Using a state management library(MobX) for React, And suppose I have a parent component which makes an http API call and updates the MobX store with the API response. Now I need that data in one of child/nested components.
My Question is, Should I pass that data as a prop to child component or should I enable child component to connect with Central Store and directly get that data ?
by connecting the child to store, I am turning the Child into a class component, which is making it more heavy and React optimisations may not apply. I am defeating the whole purpose of a functional component.
Awaiting replies.
Best Regards,
Lalit
This completely depends on the situation. I would suggest splitting your components up in 2 parts:
Components that could be re-used in other projects
(Higher level) Components that are so specific to this project that they probably never will be re-used.
For components of category 1, I would suggest not using mobx store directly, but instead make pure react components. (eg think of a dropdown, or an ajax dropdown component).
For second part components (think of, header, footer, section components specific for your website). just make them directly interact with the Mobx store, so that you can way quicker code what you need (instead of constantly having to prop everything).
addition
For components of category 1 you can always wrap them with the #inject() method. This way for example you could turn a dropdown component into a UserDropdown component that uses the mobx store for its state. (The inject method injects mobx state as props in the component).
const UserDropDownComponent = mobx.inject(stores => ({users: stores.userStore.users}))(DropDownComponent);
// usage:
<UserDropDownComponent />
Warning
For pure components wont always see changes of mobx state. To Fix this you need to wrap the component in an #observe annotation. Or you have to inject the props by wrapping it into: mobx.toJS(yourMobxStateProperty)
I'm building an application where I would like to provide separate views for same data.
In my current implementation, data is obtained by web service call and persisted in state of App component in App.js. App component hosts (renders) another component called StackEditor, which acts as a view for this.state.components in App component.
UI elements rendered by StackEditor can be moved around, and to synchronize state of App I do it as below:
<StackEditor
components={this.state.components}
onLocationChanged={this.handleLocationChanged} />
In handleLocationChanged I update the state:
handleLocationChanged(e, data) {
this.setState(prevState => {
// event data copied to state here
return {components: prevState.components};
});
}
As state is now updated, this forces StackEditor to be rendered again, as its property components is bound to state as components={this.state.components} (see in the code snippet above).
This all works, but now I started questioning if I'm doing it right.
Q1: Should I be using state instead of props?
It seems that location of component is mutated in principle, although from StackEditor point of view, it is immutable (I can decide that change is invalid and not to update the state in event listener).
Q2: Is it possible to share part of the state between 2 components in React?
If I somehow convert StackEditor from getting components from state instead of props, will I get notification on state changed by child component (StackEditor) in my parent component (App)?
Q3: Also, are props more convenient to use than state in general?
When I created another component following HOC guidelines (https://reactjs.org/docs/higher-order-components.html) I discovered that props are easily forwarded to "wrapped" component, but not state. If I provide a function to call back via property (as I did above), "wrapped" component can easily call it, without even noticing that it's "wrapped". I don't see how I can easily notify "wrapped" component about state change in "wrapper", without writing some extra code.
If you imagine your application to be a tree of components in a well designed app it's usually like this:
the leafs are stateless components . They decide how data is rendered.
the nodes are stateful components. They decide which components and data to render.
Q1: Should I be using state instead of props?
It depends on which category of components you have (node or leaf).
Q2: Is it possible to share part of the state between 2 components in
React?
If you feel that your app has a lot of state that mutates and needs to be used by several components spread over your tree you usually start to introduce an external state management library (e.g. redux). Components can subscribe to your store and become stateless as your store now handles the state.
Q3: Also, are props more convenient to use than state in general?
They solve different problems so you can't really say that. A stateless component is usually easier to understand but has no capabilities to control anything.
Also read Identify where your state should live and When to use redux.
All that is only a rule of thumb. A lot of the time you will have components that have both state and props because they control parts of your app but delegate other parts to their children.
This all works, but now I started questioning if I'm doing it right.
As far as I can see from the code you provided this looks pretty much as it has to.
I've converted most of my containers to functional components to make them reusable and I'm wondering if it's good practice to pass Action Creators as props to theses functional components.
In this case, I'm using a tab to switch between Login and Sign Up page and I'm using local state to manage active class (for styling). I call action creators passed down as props to switch between rendering a login or sign up page. I'm using this tab in several places and I'm passing a different configuration object as per use cases.
homePageTabProps = {
firstTabTitle:"blog",
secondTabTitle:"resume",
showFirstTab: this.props.showBlogTab,
showSecondTab: this.props.showResumeTab
}
Is this good practice? yay or nay?
Edit:
The active tab style is giving me problems (retains value when reusing the component). Can I have local & global state? (Sorry if this sounds stupid.)
Passing functions as a props down the hierarchy of your Components is for sure widely accepted practice. But I would suggest not to pass bare action creators, but bind them to dispatch before passing them to Component. This way, if your child Component is not connected to store it will not have to know about Redux at all (no need to manually dispatch).
As for your complication with two states. There is nothing wrong in mixing own Component's state with part of Redux passed to Component via connect. I would even strongly recommend to keep all temporary data not important to your application logic inside Component's state without exposing it to Redux. For example, most of the time, there is no sense in sending animation timings, intermediate state of user input, etc. to Redux.
But, in your case looks like indication of active tab is a direct reflection of Redux state and should be fetched from store. Otherwise it's not clear why you are sending actions to Redux on tab's change.