Skip to main content

Command Palette

Search for a command to run...

perf overhead on context flattener?

Updated
3 min read
perf overhead on context flattener?
R

If you've got any questions feel free to DM on Twitter @reillyjodonnell

Someone shared this image above. At first glance, I wasn’t sure about the perf implications — so let’s break it down from first principles :D

Let’s deconstruct this monster

Here’s the code:

// Function to flatten React Context Providers.
const flattenContextProviders = (providers) => {
  return providers.reduce((acc, Provider) => {
    return ({ children }) => (
      <Provider>{acc({ children })}</Provider>
    );
  }, ({ children }) => <>{children}</>);
};

// Array of Context Providers.
const contextProviders = [FirstContextProvider, SecondContextProvider,
ThirdContextProvider];

// Flattened Context Provider.
const FlattenedContextProvider = flattenContextProviders(contextProviders);

// ⚠️ Really bad idea for performance!
// It hides problem instead of solving it, and in really bad way.
<FlattenedContextProvider>
  <App />
</FlattenedContextProvider>

Off the top of my head I don’t know from intuition what flattenContextProviders becomes when called with the array so let’s construct it line by line:


// looks like this the first call
const flattenContextProviders = ([FirstContextProvider, SecondContextProvider,
ThirdContextProvider]) => {
  return providers.reduce((acc, Provider) => {
    return ({ children }) => (
      <Provider>{acc({ children })}</Provider>
    );
  }, ({ children }) => <>{children}</>);
};

 /*
     Here's every run for reduce

     first run 
     acc = ({children}) => <>{children}</>
     Provider = FirstContextProvider
     returns ({children}) => (
        <FirstContextProvider>
          {acc({ children })}}
        </FirstContextProvider>

     )
     since our acc is ({children}) => <>{children}</>
     which is a function that takes a parameter called children and returns 
     the children enclosed in jsx fragments

     and here in this line:
     {acc({ children })}}
     we're just calling the above function with {children} as the argument

     if that doesn't click we can work it out through substitution:
     ({children}) => (
        <FirstContextProvider>
          {
            // define a function, then CALL it immediately with { children }
            (({ children }) => <>{children}</>)({ children })
            // which becomes <>{children}</>
           }
        </FirstContextProvider>
     )

     all in the first pass we're returning
     ({children}) => (
        <FirstContextProvider>
          <>{children}</>
        </FirstContextProvider>
     )


    */

     /*
     second run
     acc = the return from the first pass:
        ({children}) => (
            <FirstContextProvider>
              <>{children}</>
            </FirstContextProvider>
         )
     Provider = SecondContextProvider

     returns ({children}) => (
        <SecondContextProvider>
          {acc({ children })}}
        </SecondContextProvider>
     )

    remember accumulator is now an anonymous function wrapping FirstContextProvider

    so the above becomes

    returns ({children}) => (
        <SecondContextProvider>
          <FirstContextProvider>
            {children}
          </FirstContextProvider>
        </SecondContextProvider>
     )    

    I think at this point you see the pattern. Once you get past that weird IIFE it's all good :D

    so at the very end flattenedContext becomes this:

     ({children}) => (
       <ThirdContextProvider>
        <SecondContextProvider>
          <FirstContextProvider>
            {children}
          </FirstContextProvider>
        </SecondContextProvider>
      </ThirdContextProvider>
     )    
    */

Perf implications

quick recap —
we have this:

const FlattenedContextProvider = ({children}) => (
       <ThirdContextProvider>
        <SecondContextProvider>
          <FirstContextProvider>
            {children}
          </FirstContextProvider>
        </SecondContextProvider>
      </ThirdContextProvider>
     )

which is an arrow function / function bound to a constant variable that returns a bunch of wrappers around children i.e. a react component.

Same rules of react apply here: don’t define components inside other components. So as long as you don’t do that there are no performance implications compared to directly wrapping your app around the same providers in the exact same location.

Other implications

What about in debugging? Does this wrapper hide the nested contexts under the name FlattenedContextProvider?

w/o wrapper

and with it

all it did was add an additional fiber “Anonymous” which we could rename via:
`FlattenedContextProvider.displayName = "FlattenedContextProvider";`

tldr;

This sort of structure — compared to manually nesting the same providers — is purely a matter of taste. It has no observability drawbacks and, most importantly, no performance overhead. :D

44 views
perf overhead on context flattener?