Too many React Context providers
Asked Answered
L

7

48

New to react here and trying to wrap my head round the new Context API (I haven't looked into Redux etc. yet).

Seems I can do much of what I need to do, but I'm going to end up with lots and lots of providers, all needing a tag to wrap my main app.

I'm going to have a provider for Auth, one for theming, one for chat messages (vis Pusher.com) etc. Also using React Router is another wrapper element.

Am I going to have to end up with this (and many more)....

<BrowserRouter>
    <AuthProvider>
        <ThemeProvider>
            <ChatProvider>
                <App />
            </ChatProvider>
        </ThemeProvider>
    </AuthProvider>
</BrowserRouter>

Or is there a better way?

Limestone answered 24/7, 2018 at 17:31 Comment(7)
This is what Redux solves.Cranston
Hmm, I was afraid somebody might say that, but I'm trying to heed the advice of those that have said to try to learn state in React before resorting to Redux. Having had a little look at Redux and MoX I think I'll be more likely to try MobXLimestone
The above is a good use case for Redux; the push back is because local state is often fine. You don't want to accept unnecessary tradeoffs. See this excellent writeup by Redux-author Dan Abramov, “You Might Not Need Redux”.Cranston
A ear a lots of people say that API Context or React hooks will put Redux to the trash but Redux is still Redux and all 3 methods should be used for different systems. In th e case or you have global store you need to affect all you website: Redux is the key, and will be ever more powerfull than API Context (by avoiding Component to ride up all the DOM (for auth or chat provider for example). Theme can be updated in Cascading like CSS so API context is a better choice.Cha
Does this pattern actually create any problems other than the fact that the list is visually long which makes the viewable page wide as well?Sydney
I like to also consider whether my entire App needs access to all the information from every context. Browers or Themes or Auths etc obviously would, but I sometimes find that some of this can be abated by putting contexts as far down as possible in the subtree.Decalogue
I think its better to have tried React Context and come to the conclusion that you need a state manager lie Redux rather than using Redux and not really knowing why. Remember useReducer can probably be a "before 3rd-party-state-manager" step.Bradbradan
C
81

If you want a solution for composing Providers without any third-party libraries, here's one with Typescript annotations:

// Compose.tsx

interface Props {
    components: Array<React.JSXElementConstructor<React.PropsWithChildren<unknown>>>
    children: React.ReactNode
}

export default function Compose(props: Props) {
    const { components = [], children } = props

    return (
        <>
            {components.reduceRight((acc, Comp) => {
                return <Comp>{acc}</Comp>
            }, children)}
        </>
    )
}

Usage:

<Compose components={[BrowserRouter, AuthProvider, ThemeProvider, ChatProvider]}>
    <App />
</Compose>

You can of course remove the annotations if you don't use Typescript.

Christology answered 19/11, 2019 at 0:11 Comment(4)
Add properties when provider need: const { components = [], children, ...rest } = props. ...... return <Comp {...rest}>{acc}</Comp>Palestrina
How to pass the value attribute to the context providers inside compose?Sheehy
Does the composing of the providers have any performance benefits or is it mainly a readability / code cleanliness thing?Puentes
Technically, this adds yet another level to the stack. So instead of reducing runtime and code complexity, this actually increases it by one more level. One could argue that the resulting code looks less convoluted, because the nesting is flattened into an array and the names only appear once, but at the cost of introducing yet another component that you need to understand and that needs to be compiled/executed/understood.Vandal
P
6

Solution with for loop:

export const provider = (provider, props = {}) => [provider, props];

export const ProviderComposer = ({providers, children}) => {
    for (let i = providers.length - 1; i >= 0; --i) {
        const [Provider, props] = providers[i];
        children = <Provider {...props}>{children}</Provider>
    }
    return children;
}

Usage:

<ProviderComposer
    providers={[
        provider(AuthProvider),
        provider(ThemeProvider),
        provider(MuiPickersUtilsProvider, {utils: DateFnsUtils}),
    ]}
>
    <App/>
</ProviderComposer>
Puncture answered 27/10, 2020 at 16:14 Comment(0)
L
3

I haven't enough reputation to comment but it could be useful integrate the rrista404 answer wrapping the component in a useCallback() hook to ensure context data integrity in some case like page switching.

// Compose.tsx

interface Props {
    components: Array<React.JSXElementConstructor<React.PropsWithChildren<any>>>
    children: React.ReactNode
}

const Compose = useCallback((props: Props) => {
    const { components = [], children } = props

    return (
        <>
            {components.reduceRight((acc, Comp) => <Comp>{acc}</Comp>, children)}
        </>
    )
}, [])

export default Compose
Lysimeter answered 3/12, 2021 at 15:17 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewTintinnabulation
S
2

Use @rista404's answer - https://mcmap.net/q/353416/-too-many-react-context-providers
as react-context-composer is deprecated.

Thanks @AO17, for the ping.


Disclaimer: I've never used this, just researched.

FormidableLabs (they contribute to many OSS projects) has a project called, react-context-composer

It seems to solve your issue.

React is proposing a new Context API. The API encourages composing. This utility component helps keep your code clean when your component will be rendering multiple Context Providers and Consumers.

Selfmoving answered 24/7, 2018 at 19:29 Comment(4)
That looks very promising. I don't entirely understand their example code (e.g. WTH is Context<Theme>?), but I'm going to give it a go and will mark as correct if it works. Many thanks.Limestone
@Limestone The example uses flow according to this example, github.com/jamiebuilds/create-react-context#exampleSelfmoving
the package has been archived, use the solution from @ChristologyJulijulia
@AO17 Much appreciated for heads-up~ Updated the answer.Selfmoving
V
1

One simple solution for this is to use a compose function, like the one Redux uses, to combine all the providers together. Then the compose function would be called like so:

const Providers = compose(
    AuthProvider,
    ThemeProvider,
    ChatProvider
);

also I haven't used this solution but with React's new hooks feature, instead of rendering your contexts, you can use the react hook to access it in the function definition.

Vicarious answered 20/3, 2019 at 3:48 Comment(3)
I tried this but kept getting TypeError: Cannot call a class as a functionOchre
Page not found when trying to access the linkImmortality
It's now a TypeScript file (compose.ts) github.com/reduxjs/redux/blob/master/src/compose.tsAttendance
M
1

Few lines of code solve your problem.

import React from "react"
import _ from "lodash"

/**
 * Provided that a list of providers [P1, P2, P3, P4] is passed as props,
 * it renders
 *
 *    <P1>
        <P2>
          <P3>
            <P4>
              {children}
            </P4>
          </P3>
        </P2>
      </P1>
 *
 */

export default function ComposeProviders({ Providers, children }) {
  if (_.isEmpty(Providers)) return children

  return _.reverse(Providers)
    .reduce((acc, Provider) => {
      return <Provider>{acc}</Provider>
    }, children)
}
Mattingly answered 29/10, 2019 at 0:27 Comment(0)
F
0

recompose js nest helper if you need inject external props to provider elemet use withprops hoc

Ferrotype answered 12/6, 2021 at 20:42 Comment(1)
Welcome to StackOverflow! When you reply to a question remember to provide clear and documented answers. Add code fragments to explain what you mean and/or provide valuable links that give further clarifications on the solution you posted!Consumable

© 2022 - 2024 — McMap. All rights reserved.