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';
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.
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
From React documentation.
Conceptually, components are like JavaScript functions. They accept
arbitrary inputs (called “props”) and return React elements describing
what should appear on the screen.
Considering:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
or
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Will give us the ability to do this:
<Welcome name="Luke" />;
<Welcome name="Leia" />;
to use as we wish in the DOM,
Hello, Luke
Hello, Leia
Now when people prescribe props shouldn't be changed, it would make sense the reason is in my thinking would be like the same as changing the values of attributes of an image tag?
HTML:
<img id="Executor" alt="Picture of Executor" src="/somepath/vaders-star-destroyer-executor.jpg"/>
JS:
Meanwhile in a Javascript file a long time ago in a galaxy far, far away...
var imageOfVadersStarDestroyer = document.getElementById('Executor');
imageOfVadersStarDestroyer.src = "/somepath/vaders-star-destroyer-avenger.jpg"
Because if we keeping changing an elements attribute values this can cause confusion and slower renderings?
So is the reason why the prescription is to never change props in React is because is the library is trying to make elements as predictable as possible?
Setting props outside of React is dangerous and should be avoided. Why? The main reason is that it doesn't trigger re-renders. Hence bugs and unexpected behaviour.
Re-rendering
Most of the time, props are data that is store as state in the parent component, which is manipulated by calling setState() (or the second function returned by React.useState()). Once setState() is called, React re-renders and computes what has changed under the hood, with the latest props and state. Manually assigning values to props, therefore won't notify React that the data has changed and something has to be re-rendered.
The good practice
Making props read-only allows React components to be as pure as possible, which is obviously a good practice anyway even when writing plain JS. Data won't be changed unexpectedly and can only be done so by calling setState() (You might have heard of the single source of truth, which is what React is trying to leverage).
Imagine you notice something went wrong in the app and the data shown to the end user is completely different from the source, it would be a pain trying to find out where the data has been manipulated wouldn't it? :)
never change props in React
means that you should never do this.props.name = "userName" because of React's one way data binding, props are read only, to update a component's props, you should pass a function from the parent that will do that ( in the parent ) , or dispatch an action if you're using redux, a change in the props will trigger a re-render
props is a constant in this case. You will always need it in your components.
But there is a cleaner way to write it or even omit it.
Regular way with Function Expression (same as your exemple)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
ES6 Object Destructing - explicit
function Welcome(props) {
const {name} = pros
return <h1>Hello, {name}</h1>;
}
ES6 Object Destructing - inplicit, cleaner way
function Welcome({name}) {
return <h1>Hello, {name}</h1>;
}
And of course, you can use the class way which requires the usage of this.props.yourAttr
However, in the new version 3 of create-react-app, changed class components to functional components. You can see this exact modification on Github here.
You can need to learn more about destructing assignment in the old and good MDN linked here or an in-depth approach both array and object destructuring here.
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.
I'm trying to build some modular SAP so many teams can work separatelly.
Basically, I want my containers to be independent in terms of container, store, reducers, sagas.
The actual question is (example code):
I render a basic template:
<div>
<a onClick={emitLoadUserListAction}>Load user list</a>
<UserList/>
</div>
At this point, I make use of 1 reducer for UserList to keep the array of users (empty at the beginning).
Let's assume I have a saga, waiting for this data to come as a user list in a json.
Store:
{
UserList: []
}
Once the saga fetches the data, publishes an action modifiying the current store:
Store:
{
UserList: [{name:"john",counter:0},{name:"pepe",counter:0}]
}
Now my UserList component can list this as we have the mapStateToProps pointing to this part of the store.
this.props.userList.map ( (userData,i) => { return <User data={userData}> } ))
So now everything is working like a charm if User component is just a normal component.
But what if User is actually a container, which is expecting to work on its own, with its own state I didn't connected yet via its own reducer. I don't want his parent to manage it. I want user to be independent as I could pass its location in the store with reselect selector or similar, or I could just pass the index in the array as a prop, so I could be the selector. This way I would have store injected in props, but I won't have reducer.
I'm pretty sure many of you already pass through this but I couldn't find a proper answer.
As you can see the idea is to have a component, which is loading on demand, not in the initial combineReducers, not handled by its parents, just render, and reducer injected to work on its own.
If I could have just a way to load its reducer on demand then, I would not store the data in the UserList but it will be a composition of reducers.
Thanks a lot in advance.
I'm continuing on from my comment and the question that followed so I can expand on it without the restrictions of the comments section.
Yes, my library calls replaceReducer on the store to in order to, well, replace the reducer with the new one included. In order to do so, I provide a Higher-Order Component (HOC) which bundles the component with it's associated reducer and performs the replacement when it is mounted.
The interface looks something like this:
export const MyBundledComponent = bundle(MyComponent, myReducer)
The only requirement for it to work is that the component is mounted within a Provider from react-redux. This gives the HOC access to the store on React's context the same way the connect HOC does. This isn't really a very prohibitive restriction though, as most redux apps have a Provider at the top of the tree already.
Hope this helps.
So far I found resources like this:
https://medium.com/#jimmy_shen/inject-reducer-arbitrarily-rather-than-top-level-for-redux-store-to-replace-reducer-fdc1060a6a7
which allow you to inject reducers on demand by replacing the main reducer by using the Redux store API store.replaceReducer(nextReducer)
The problem with this solution is the need to have access to the main store object from the child component that should be encapsulated.
For the moment working not ideal solution that I found is to deliver the encapsulated component with a "multiple components reducers" meaning that the reducer assumes there could be more than one component under the same parent where each one has different ids.
So each action should check the payload ID, in order to get the state from the store object.
This would mean a small change in the hierarchy as the component would not be child but sibling.
Following the previous example, imagine that we list a shallow version of the user list and then you show more data once u click on any user:
`
Store: {
UserList: [], // basic info, id plus minimal data
users: {} --> userReducer // listing each user by key
}
`
This way the user component will expose multiUserReducer instead of logic for just one.
This obviously means the reducer is loaded in advance, even if you never load any user componet.