DEV Community

Michał Kardyś
Michał Kardyś

Posted on

4 3

Singleton-like Context for shared components managent

(story originally appeared at kardys.dev)

Are you handling external modules or shared config in your React projects?

React Context, when overused, can become hell. On the other hand, Setting up shared modules/config with Context can be helpful.

How to handle shared config?

Regular Context is hidden by Provider down the render tree.

What if we... make a singleton?

Single place for your settings is helpful. You have easy go-to place if you need to update your config. Yet, with increasing modularity of your code it becomes harder and harder.

So, should you setup Redux workflow?

If the app is not big/complex Redux is not goto. It's like shooting a pigeon with a cannon. Overkill.

What then?

Single source of truth would be helpful. A singleton.

How to do it?

Let's inverse our context! Let's prevent providers down the tree. Let's...

Create context singleton

Simplest implementation for singleton-like context:

const NOT_INSTANTIATED = 'NOT_INSTANTIATED';

const Context = React.createContext(NOT_INSTANTIATED);

function SingletonContext(props){
  const value = React.useContext(Context)

  if(value === NOT_INSTANTIATED){
    return <Context.Provider {...props}/>
  }

  return <React.Fragment {...props}/>
}
Enter fullscreen mode Exit fullscreen mode

What happens here?

You create React context with defaul "NOT_INSTATIATED" value. So, if use consume the context and no provider is rendered above - you get the default value.

Next is wrapper.

SingletonContext does following:

  1. Consumes provider
  2. If it hasn't been instatiated earlier create provider
  3. Otherwise return Fragment

Singleton in action

const Display = () => {
  const value = React.useContext(Context)

  return <div>{value}</div>;
};

const App = () => {
  return <React.Fragment>
    <SingletonContext value={'first'}>
      <SingletonContext value={'second'}>
        <Display/>
      </SingletonContext>
    </SingletonContext>
  </React.Fragment>
}
Enter fullscreen mode Exit fullscreen mode

When we create 2 SingletonContext components, the former Context.Provider is created

Result is:
first is passed to Display consumer

What if we create parallel Provider?

const App = () => {
  return <React.Fragment>
    <SingletonContext value={'first'}>
      <SingletonContext value={'second'}>
        <Display/>
      </SingletonContext>
    </SingletonContext>

    <SingletonContext value={'separate render tree'}>
      <Display/>
    </SingletonContext>
  </React.Fragment>
}
Enter fullscreen mode Exit fullscreen mode

Now we have, as expected, 2 results:

  • first
  • separate render tree

That why it's not fully singleton (unless you put it in app root).

Use cases

  1. Config shared between many apps
  2. Redux singleton (we can render redux provider similar way)
  3. Many components loosely scattered
    • each needing a common theme provider or so
    • this way we can 'safeguard provider' and render it if not present

It is, of course, not 'the only right way' to do things.

For external modules you might want this method from my previous post, too.

How do you manage your configs?

See code in action:
codepen

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay