My question is "Is there a way to avoid sending callbacks deep in the tree when I want to access/react upon child components data".
I have a component Application that contains a child component Person which in turn contains a child component TraitCollection which in turn contains a child component Trait.
When Trait is changed I want to send data back to Application to be forwarded to another child in Application named PersonList.
My current solution is to send callbacks as props to each child. Each callback function constructs and appends the data which finally reaches Application where I pass it down as a prop to PersonList.
I can imagine that "adding more levels", or splitting components up in more smaller parts will further complicate this callback chain.
Is there any other way to do this or is this the best way to handle passing data from children to parents in React?
The way you are handling it is the recommended way and the most React-like. There is nothing wrong with that approach by itself other than the problem you have found. It looks like Redux could help you solve that problem. Redux lets you unify the state of your React application with the usage of a common store and a good design pattern based on action creators, actions and reducers.
You can use React Context directly https://facebook.github.io/react/docs/context.html, but better option will to use React-Redux, it will allow you to avoid passing callbacks and you will be able to change state of any component from any component (connected to the store) with dispatch function.
The docs answer this directly:
In large component trees, an alternative we recommend is to pass down a dispatch function from useReducer via context
From: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
(Archive)
To avoid Deep Callbacks, according to React documentation, useReducer via context as below:
How to avoid passing callbacks down?
We’ve found that most people don’t enjoy manually passing callbacks through every level of a component tree. Even though it is more explicit, it can feel like a lot of “plumbing”.
In large component trees, an alternative we recommend is to pass down a dispatch function from useReducer via context:
const TodosDispatch = React.createContext(null);
function TodosApp() {
// Note: `dispatch` won't change between re-renders
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
Any child in the tree inside TodosApp can use the dispatch function to pass actions up to TodosApp:
function DeepChild(props) {
// If we want to perform an action, we can get dispatch from context.
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
This is both more convenient from the maintenance perspective (no need to keep forwarding callbacks), and avoids the callback problem altogether. Passing dispatch down like this is the recommended pattern for deep updates.
Related
While I can access the store using thunks and/or global stores, I don't want to bind my component to the Redux store. Because the component will be used with other stores both inside and outside the project. The component has a number of children and passes props to them via the context API.
Since context APIs cause rerendering on child components, we employ the below approach to pass data from redux store to context APIs. This way, I can use dataRef.current whenever I need to access data in the redux store:
export const useData = () => {
const dataRef = React.useRef();
useSelector((state) => {
dataRef.current = state;
});
return dataRef;
};
This works perfectly fine, But my concern is whether it will cause memory leaks or any other unknown problems.
It will likely cause things to not rerender in the right moment - since you leave the whole world of "letting React rerender" at that point in time. A change in the Redux store will not rerender your component any more.
I think you need to reevaluate why you are doing this. This technique seems to bind your component to the Redux store just as much as just calling useSeletor would do. As long as your state structure has the value in the path you expect, you could also just call useSelector directly without any weird workarounds. That would work with any Redux store being present at that point in time.
When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)
I'm still pretty new on React development, but I've already work on 3 big project using React+Redux and I see a pattern that I dislike a lot:
componentWillReceiveProps(nextProps) {
if (nextProps.params.type === TYPE_NEW_USER) {
this.modalUsername = this.props.showPopup( < NewUsernamePopup onClose = {::this.closeUsernamePopup
}
/>, USERNAME_POPUP_ID, true);
}
if (this.state.kind !== nextProps.kind || this.state.filter !== nextProps.filter || this.state.hashtags !== nextProps.hashtags) {
this.setState({
results: [],
loading: true,
kind: nextProps.kind,
filter: nextProps.filter,
hashtags: nextProps.hashtags
}, () => this.manageResults(nextProps.results, false));
} else {
this.manageResults(nextProps.results, true);
}
this.managePages(nextProps.paging);
}
I would like to avoid the ifs inside the componentWillReceiveProps. How do you handle it? We've analysed another project using Flux and callback registration. It looks like:
componentWillMount() {
EntityStore.on(EntityActions.ENTITIES_LOADED, this.getData.bind(this));
EntityActions.entitiesLoaded();
}
The first event is emitted by the component, but afterwards the store emits the event and the component updates. Additionally a single store keeps its state and do not duplicate async calls if it already has the content. I personally like to avoid the ifs, but I do NOT want to lose Redux (its community and tools).
How would you add the current logic (ifs) inside the componentWillReceiveProps outside the component? I would like to handle the logic in a service layer and not inside the component.
I would definitely appreciate to read your opinion around this, because I've been struggling to find a solutions that fits.
The redux approach is to put the logic into the actions/reducers.
So i don't know what your manageResults method does, but it is probably the piece of logic you want to move into a reducer so you won't need to call it from your component anymore.
So the kind,filter and hashtagsvariables should be updated from redux actions only.
tl;dr properly following redux best practices would eliminate some of these conditions, but I'd be more concerned about the overall design this snippet is revealing.
To address the individual lines:
if (nextProps.params.type === TYPE_NEW_USER) {
This looks like a redux action was passed to the component? If so, that's not great, only the reducers should care about action types.
this.modalUsername = this.props.showPopup(
The lifecycle hook componentWillReceiveProps is not the right place to initiate things like that, the resulting React component in an instance var also looks quite weird.
if (this.state.kind !== nextProps.kind || this.state.filter (etc.) ) {
If you have UI state in this component that is somehow dependant on the props coming from redux, these types of ifs are somewhat necessary, since you can't do it outside the component.
You are right to dislike this "pattern", which seems to reflect bad overall design. This component seems to be involved with "pages", "results", a username, and some ajax fetching with a loading flag. Can only speculate of course, but it seems like it's doing too much. The ajax request lifecycle should definitely be modelled in a reducer.
That said, the lifecycle hooks do often contain a bunch of ifs, since the reducers don't see routing and which components get mounted/unmounted, so that's where you have to react to changing props sometimes.
I came across an example, where the mapStateToProps function is using memoization. I was just wondering how the "state" parameter is passed onto memoized selectors. After looking at the docs for reselect as well as redux, it seems that the mapStateToProps can return a function which accepts state as its argument, and the connect decorator might be the one passing the state to it but am not sure. Can someone please shed some light?
views/tracklist/index.js
const mapStateToProps = createSelector(
getBrowserMedia,
getPlayerIsPlaying,
getPlayerTrackId,
getCurrentTracklist,
getTracksForCurrentTracklist,
(media, isPlaying, playerTrackId, tracklist, tracks) => ({
displayLoadingIndicator: tracklist.isPending || tracklist.hasNextPage,
isMediaLarge: !!media.large,
isPlaying,
pause: audio.pause,
pauseInfiniteScroll: tracklist.isPending || !tracklist.hasNextPage,
play: audio.play,
selectedTrackId: playerTrackId,
tracklistId: tracklist.id,
tracks
})
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Tracklist);
core/tracklists/selectors.js
export function getCurrentTracklist(state) {
// console.log(state);
let tracklists = getTracklists(state);
return tracklists.get(tracklists.get('currentTracklistId'));
}
export const getTracksForCurrentTracklist = createSelector(
getCurrentPage,
getCurrentTrackIds,
getTracks,
(currentPage, trackIds, tracks) => {
return trackIds
.slice(0, currentPage * TRACKS_PER_PAGE)
.map(id => tracks.get(id));
}
);
Overview of how state is passed down to a selector when we use the Connect component from react-redux
What is a selector?
A selector extracts a subset of data from a source.
Let us think of the Redux store as our 'front end database'. For the purposeIn a database if you want to extract a subset of the total data you execute a query. In a similar fashion selectors are our queries to the Redux store.
In the simplest case, a selector could just return the state of the entire store.
The reselect docs give us three great reasons to use selectors
Selectors can compute derived data, allowing Redux to store the
minimal possible state.
Selectors are efficient. A selector is not
recomputed unless one of its arguments change.
Selectors are
composable. They can be used as input to other selectors.
What is a higher order component?
A higher-order component is a function that takes an existing component and returns a new component.
Connect is a higher order component that be given a selector
Taken from this brilliant gist which gives a good explanation of connect.
connect() is a function that injects Redux-related props into your
component.
Connect is a higher order component that makes our React component know about the Redux store. When we call connect we can pass mapStateToProps and mapDispatchToProps. These functions define the way in which our new component will be connected to the redux store.
We can give it access to state by passing a mapStateToProps function as an argument.
We can also bind action creators to store.dispatch through mapDispatchToProps. The advantage of this is that we don't need to pass down the entire store in order for a component to have access to store.dispatch so that the component can dispatch its own Redux actions.
The mapStateToProps function we pass to Connect is a selector
From the react-redux docs
The mapStateToProps function takes a single argument of the entire
Redux store’s state and returns an object to be passed as props. It is
often called a selector.
Think of the object that is returned by mapStateToProps as the result of our query to the Redux store. The resulting
The mapStateToProps function should normally return a plain object.
The result of calling mapStateToProps will normally be a plain object representing the data we extracted from the redux store.
The higher order Connect component allows us to extend the functionality of an existing component by merging in the data from this new object with the component's existing props.
Since selectors are just functions we can connect them to the Redux store using the connect component as well.
However in some cases we can return a function. Why would we do this?
By returing a function in mapStateToProps we can hijack the rendering cycle of components and optimise performance
In advanced scenarios where you need more control over the rendering
performance, mapStateToProps() can also return a function. In this
case, that function will be used as mapStateToProps() for a particular
component instance. This allows you to do per-instance memoization.
By passing the mapStateToProps function as an argument to our higher order component our connected component will be updated anytime the some state has changed in the Redux store.
If these updates happen very frequently or the state tree is large then the reselect library is useful as it allows us to use memoized selectors.
This fancy word means that results of selector calls are stored in case they need to be retrieved again.
So if mapStatesToProps returned a plain object instead of a function then whenever our store state changed then we would have new props for our component.???
Connecting selectors to the store
If you are using React Redux, you can call selectors as regular functions inside mapStateToProps():
import { getVisibleTodos } from '../selectors'
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
Sharing Selectors Across Multiple Components
We can give reselect selectors props just like components when using the reselect library. This allows us to share selectors across multiple components.
Say we have multiple toDo lists each with their own Id. We would still use the same getVisibleTodos selector for each toDo list instance but just pass a different id as a prop.
However the issue with this is that createSelector only returns the cached value when its set of arguments is the same as its previous set of arguments.
The reselect docs point out that we can overcome this limitation by returning a function inside mapStateToProps:
In order to share a selector across multiple components and retain
memoization, each instance of the component
needs its own private copy of the selector.
If the mapStateToProps argument supplied to connect returns a function
instead of an object, it will be used to create an individual
mapStateToProps function for each instance of the container.
By returning a function inside mapStateToProps we can overcome this limitation and memoization will work correctly.
For a more detailed explanation see this
Is so simple: let's give you an example, I have a mapStateToProps like this:
function mapStateToProps(state) {
return {
categoryHistory: getCategoryHistory(state,'extended')
}
}
then I've create a selector like this:
export const getCategoryHistory = (state, type) => createSelector([getTaxonomy, selectedCategoryID], (categories, categoryID) => categories.getIn([type, categoryID]) || [])(state)
The solution is to call createSelector() passing the state as parameters:
createSelector()(state)
inside the selector you can use all the parameter you want to pass through.
In the cases you mentioned, mapStateToProps is a function which takes in state and returning object. When you passed mapStateToProps to connect, you passed a function which accepts state provided by connect as its argument.
createSelector creates a function which can take in state and returning object as well. Therefore you can assign it to mapStateToProps and pass it into connect.
In documentation, you'll normally find the following:
const mapStateToProps = (state) => {
whatever code
}
and
export default connect(mapStateToProps, mapDispatchToProps)(Component)
where mapStateToProps takes in state argument which is provided by connect.
However, one can let mapStateToProps to be a selector as followed:
const mapStateToProps = createSelector(
whatever code
)
This is because createSelector can take in a state as followed:
createSelector(whatever code)(state)
and return an object, just like what you find a mapStateToProps does in documentation.
How do people typically approach having "global" data in a React application?
For example, say I have the following data for a user once they're logged into my app.
user: {
email: 'test#user.com',
name: 'John Doe'
}
This is data that almost any component in my app might like to know about - so it could either render in a logged in or logged out state, or perhaps display the users email address if logged in.
From my understanding, the React way of accessing this data in a child component is for a top level component to own the data, and pass it to child components using properties, for example:
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2 user={user}/>
</Page2>
</App>
But this seems unwieldy to me, as that would mean I'd have to pass the data through each composite, just to get it to the child that needed it.
Is there a React way of managing this type of data?
Note: This example is very simplified - I like to wrap intents up as composites so implementation details of entire UI features can be drastically changed as I see fit.
EDIT: I'm aware that by default, calling setState on my top level component would cause all child components to be re-rendered, and that in each child component I can render using whatever data I like (e.g. global data, not just state or props). But how are people choosing to notify only certain child components that they should be rendered?
Since I originally answered this question, it's become apparent to me that React itself doesn't support "global" data in any sense - it is truly meant to manage the UI and that's it. The data of your app needs to live somewhere else. Having said that, it does now support accessing global context data as detailed in this other answer on this page. Here's a good article by Kent Dodds on how the context api has evolved, and is now officially supported in React.
The context approach should only be used for truly global data. If your data falls into any other category, then you should do as follows:
Facebook themselves solve this problem using their own Flux library.
Mobx and Redux are similar to Flux, but seem to have more popular appeal. They do the same thing, but in a cleaner, more intuitive way.
I'm leaving my original edits to this answer below, for some history.
OLD ANSWER:
The best answer I've found for this so far are these 2 React mixins, which I haven't had a chance to try, but they sound like they'll address this problem:
https://github.com/dustingetz/react-cursor
and this similar library:
https://github.com/mquan/cortex
MAJOR NOTE: I think this is a job for Facebook's Flux, or something similar (which the above are). When the data flow gets too complex, another mechanism is required to communicate between components other than callbacks, and Flux and it's clones seem to be it....
Use the React Context Property This is specifically for passing global data sets down the chain without explicitly forwarding them. It does complicate your Component lifecycle functions though, and note the cautions offered on the page I've linked.
You can use the React Context API for passing global data down to deeply nested child components. Kent C. Dodds wrote an extensive article on it on Medium React’s ⚛️ new Context API. It'll help in getting a better understanding of how to use the API.
I think React.createContext() is perfect solution for your purpose.
React will re-render only components, that listen context changes with useContext hook.
Here is a simple snippet for your code:
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
In case if you want to control re-renders directly, you can use React.memo in nested components. For example, if you need re-render component only after specific attribute change.
Also, with nesting context values, you can reach good flexibility of your app. You can pass different context values for different part of your application.
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
const AnotherUser = getAnotherUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<CurrentUser.Provider value={AnotherUser}>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</CurrentUser.Provider>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
What's wrong with just passing data all the way down the component chain via rendering all children with {...restOfProps}?
render(){
const {propIKnowAbout1, propIKnowAbout2, ...restOfProps} = this.props;
return <ChildComponent foo={propIKnowAbout1} bar={propIKnowAbout2} {...restOfProps}/>
}
There is Reactn https://www.npmjs.com/package/reactn
You use this.global and this.setGlobal to get and set the global state same as you do with the local state.
To be able to do so you only need to
import React from 'reactn';