DEV Community

Cover image for Freakin Easy React State Management with useProvider Hook
Jonathan Gamble
Jonathan Gamble

Posted on • Updated on

Freakin Easy React State Management with useProvider Hook

If you've ever used Svelte, and you know how easy the getContext and setContext apis are, then you might get a little jealous when you go back to React.

I'm not a huge fan of React, but I figured I would help some of you who get into React Context Hell. Some things get easier thanks to NextJS, but I don't see React working too hard to fix some of the classic problems like lack of signals and context hell.

So, here is a fix. How about one global provider that is just easy?

Installation

This is the core hook.

use-provider.tsx

'use client'

import { FC, ReactNode, createContext, useContext } from "react";

const _Map = <T,>() => new Map<string, T>();

const Context = createContext(_Map());

export const Provider: FC<{ children: ReactNode }> = ({ children }) =>
    <Context.Provider value={_Map()}>{children}</Context.Provider>;

export const useProvider = <T,>(key: string) => {

    const context = useContext(Context);
    return {
        set value(v: T) { context.set(key, v); },
        get value() {
            if (!context.has(key)) {
                throw Error(`Context key '${key}' Not Found!`);
            }
            return context.get(key) as T;
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

Import and add <Provider /> to your home component:

page.tsx

'use client';

import Test from "./test";
import { Provider } from "./use-provider";

export default function Home() {

  return (
    <Provider>
      <main>
        <p>Parent</p>
        <Test />
      </main>
    </Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Usage

Import useProvider just like any other hook. Pass in the key you want to use for your object. Your value can be anything and any type. It will return get and set functions. Use them wherever you like in your app!

test.tsx

'use client'

import TestChild from "./test-child";
import { useProvider } from "./use-provider";

export default function Test() {

    const user = useProvider<string>('user');

    user.value = 'Jonathan';

    return (
      <>
        <TestChild />
      </>
    )
  }
Enter fullscreen mode Exit fullscreen mode

test-child.tsx

'use client'

import { useProvider } from "./use-provider"

export default function TestChild() {

    const user = useProvider<string>('user');

    return (
        <>
            <p>{user.value}</p>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

You can use as many providers as you like, just like you would with context. However, if you need more than one your probably really just need to pass / share an object instead.

Note: - This does not mean the values will be reactive. You need to use useState or useReducer concurrently with useProvider just like you normally would with useContext.

Sure, you could create a complex reactive store on top of this (which I may write up later), but the point of this post is to make sharing context... well, easy.

If you like this pattern, see:

J

Top comments (0)