-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How will react solve nested contexts? #14620
Comments
The upcoming Hooks API provide a different way to consume contexts. |
I'll be honest. If you're encountering this kind of implementation, then your architecture design seems poor and you probably shouldn't be using React context. |
What does that library have to do with 5 layers deep of providers/consumers? |
In that case, the context handling is now on the users of the library, and less on the library. How they utilize the features is up to them, and if they want all providers in one place (which defeats the purpose of having multiple stores), then that's their choice. Ideally a multiple store solution would be implemented at different splits in the application, so nested contexts like this are much more rare. My 2 cents at least. |
Not at all. But it's also hard to discuss your problem without a more realistic example. Please create one? |
What would your ideal syntax be? |
Consuming multiple contexts without nesting is already supported. (With Hooks.) Context.write has an RFC open for it. We don't know if it'll get through because it raises some very complicated questions. But while the RFC is open I'm not sure what's actionable in this issue. Do you have something to add beyond what's already in the RFC motivation? |
@rabbitooops Which exactly issues with hooks do you have? I use hooks in production and they work well for my team. |
@rabbitooops reactjs/rfcs#101 |
@rabbitooops How about using a single store and Symbol as keys to mimic multi-layers of store?
Or in a imperfect way in javascript:
|
@zhujinxuan You can use Unstated, example: <Subscribe to={[AppContainer, CounterContainer, ...]}>
{(app, counter, ...) => (
<Child />
)}
</Subscribe> |
We're preparing it for a release within a week or two — not sure why you inferred that. They'll be ready soon although if you want to be safe, please wait until a stable release.
Calling Hooks in a loop (as you do in
Both Overall, I'm struggling to understand the intent of this issue. Consuming multiple contexts is solved by Putting multiple providers can be seen as "boilerplate" and we might eventually have a solution for this. But I also don't understand why you see it as a big problem. Examples in this thread aren't realistic enough to explain the issue to me. I don't see anything bad with nesting several layers of JSX somewhere at the top of the application. Pretty sure you have much deeper I'll close this as I think I already replied to these points, and the discussion goes in circles. If there's something missing let me know. |
OT: @gaearon, there is any plan to add something like useRender(() => <div />, [...props]) The second arg has the same role of |
See second snippet in https://reactjs.org/docs/hooks-faq.html#how-to-memoize-calculations. |
I ended up with a code like that: function provider<T>(theProvider: React.Provider<T>, value: T) {
return {
provider: theProvider,
value
};
}
function MultiProvider(props: {providers: Array<{provider: any; value: any}>; children: React.ReactElement}) {
let previous = props.children;
for (let i = props.providers.length - 1; i >= 0; i--) {
previous = React.createElement(props.providers[i].provider, {value: props.providers[i].value}, previous);
}
return previous;
} Then in my top-level providing component: public render() {
return (
<MultiProvider
providers={[
provider(Context1.Provider, this.context1),
provider(Context2.Provider, this.context2),
provider(Context3.Provider, this.context3),
provider(Context4.Provider, this.context4),
provider(Context5.Provider, this.context5),
]}
><AppComponents />
</MultiProvider>
}
I have ~15 dependencies that I want to be injectable in that manner, and having 15 levels of indentation doesn't look pretty to me :) |
@0xorial you dont really need to have a component for that, after all <></> is just a function call React.createElement. So you could simplify it to a compose function like: const compose = (contexts, children) =>
contexts.reduce((acc, [Context, value]) => {
return <Context.Provider value={value}>{acc}</Context.Provider>;
}, children); and use it as: import Context1 from './context1';
import Context2 from './context2';
import Context3 from './context3';
...
import Context15 from './context15';
const MyComponent = (props) => {
// const value1..15 = ... get the values from somewhere ;
return compose(
[
[Context1, value1],
[Context2, value2],
[Context3, value3],
...
[Context15, value15],
],
<SomeSubComponent/>
);
} |
I wrote a library in the past that handles this case: https://github.com/disjukr/join-react-context |
this is definitely something that happens in applications all the time. |
Here is a closure alternative of @alesmenzelsocialbakers solution: const composeProviders = (...Providers) => (Child) => (props) => (
Providers.reduce((acc, Provider) => (
<Provider>
{acc}
</Provider>
), <Child {...props} />)
)
const WrappedApp = composeProviders(
ProgressProvider,
IntentsProvider,
EntitiesProvider,
MessagesProvider
)(App)
ReactDOM.render(<WrappedApp />, document.getElementById('root')); Downside is that you have to write each specific Provider component. export const ProgressProvider = ({ children }) => {
const [progress, setProgress] = useState(0)
return (
<ProgressContext.Provider value={{ progress, setProgress }}>
{children}
</ProgressContext.Provider>
)
} |
I have created a state management library that is better at service composition. Here is a demo of avoiding provider hell. Feel free to try it or read its source(100 lines of code)! It introduce a "scope" object to collect the context provider, so that:
|
I took a crack at this as well. This seems to work fine: const composeWrappers = (
wrappers: React.FunctionComponent[]
): React.FunctionComponent => {
return wrappers.reduce((Acc, Current): React.FunctionComponent => {
return props => <Current><Acc {...props} /></Current>
});
} Usage is: const SuperProvider = composeWrappers([
props => <IntlProvider locale={locale} messages={messages} children={props.children} />,
props => <ApolloProvider client={client}>{props.children}</ApolloProvider>,
props => <FooContext.Provider value={foo}>{props.children}</FooContext.Provider>,
props => <BarContext.Provider value={bar}>{props.children}</BarContext.Provider>,
props => <BazContext.Provider value={baz}>{props.children}</BazContext.Provider>,
]);
return (
<SuperProvider>
<MainComponent />
</SuperProvider>
); I also published this helper as an npm library |
The following shows how I am passing around the authenticated user to components that need it. I decided to create one state for my application. In my State.js file I set up my initial state, context, reducer, provider, and hook. import React, { createContext, useContext, useReducer } from 'react';
const INITIAL_STATE = {}
const Context = createContext();
const reducer = (state, action) =>
action
? ({ ...state, [action.type]: action[action.type] })
: state;
export const Provider = ({ children }) => (
<Context.Provider value={ useReducer(reducer, INITIAL_STATE) }>
{ children }
</Context.Provider>
);
const State = () => useContext(Context);
export default State; Then in my index.js file I wrapped my app in the provider. import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './State';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<Provider>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root'),
); To consume the state in a component I can use the hook. I can also use dispatch to update the state. For example if I want to get or set a user. import React, {useEffect} from 'react';
import State from './State'
const ExampleComponent = () => {
const [{ user }, dispatch] = State();
useEffect(() => {
const getUser = async () => {
const data = await fetch('http://example.com/user.json'); // However you get your data
dispatch({ type: 'user', user: data });
}
getUser();
}, [dispatch]);
// Don't render anything until user is retrieved
// The user is undefined since I passed an empty object as my initial state
if(user === undefined) return null;
return(
<p>{user.name}</p>
);
}
export default ExampleComponent; I think this way gives me the freedom to build the state how I need it without adding a ton of extra contexts and helps me to avoid a deep nest of providers. |
How to do I use this in a class component ? |
Aren't hooks used to take advantage of various React features without writing classes? |
I created a package to solve the problem by provide similar API with vue3 https://github.com/TotooriaHyperion/react-multi-provide
notice:
Outer.tsx
Inner2.tsx
|
here is how I do it: interface Composable {
(node: React.ReactNode): React.ReactElement
}
const composable1: Composable = (node)=>{
return <someContext.Provider>{node}</someContext.Provider>
}
function Comp({children}:{children?:React.ReactNode}){
return pipe(
composabl1, composabl2, composable3
)(children)
} You can find the |
From all the above proposed solutions to wrapping components with multiple providers, this one by @alesmenzelsocialbakers is the most performant and neat one |
well I have a problem with context, this is my code |
👉 https://github.com/nanxiaobei/react-easy-contexts An introduction article: |
I published package that provides type-safe way to merge providers flat. |
This is a fun solution too: https://dev.to/alfredosalzillo/the-react-context-hell-7p4 |
I have completely resolved the issue. const valid: FC = () => (
<MergeProvider
contexts={[
[createContext(0), 0],
[createContext("string"), "string"],
]}
/>
);
const invalid: FC = () => (
<MergeProvider // type error!
contexts={[
[createContext(0), "0"], // mismatch
[createContext("string"), "string"],
]}
/>
); The implementation of |
The text was updated successfully, but these errors were encountered: