dai-shi / use-context-selector

React useContextSelector hook in userland
https://www.npmjs.com/package/use-context-selector
MIT License
2.67k stars 61 forks source link

Possible to select multiple values and form an object? #140

Open NotYoojun opened 2 weeks ago

NotYoojun commented 2 weeks ago

Hey,

I have a context object that contains multiple things:

export type MyStoreType = 
{
    currentColor: string;
    currentTool: "pan" | "pen" | "eraser";
    currentView: number;
}

export const MyStoreContext = createContext<MyStoreType>(...)

Actually, the MyStoreType is much more complex than the example above. Now in a consumer component, I want to use 'currentColor' and 'currentView' property, and I don't want re-renders caused by the 'currentTool' property. I tried to wrap a more advanced selector, this selector looks like this:

/** Use this hook, get all the values of the context. */
function useHook(): Readonly<MyStoreType & { Update: /* something else */}>
/** Use a selector function to select a part of the context. */
function useHook<TSelectedObject>(selector: (value: TFullValue) => TSelectedObject): Readonly<TSelectedObject> & { Update: /* something else */}>
/** Use an array of properties to select a part of the context. */
function useHook<U extends keyof TFullValue>(selections: Array<U>): Pick<TFullValue, U> & { Update: /* something else */}>

function useHook(selector?: any)
{
    const selectFunc = useCallback((v: MyStoreType>
        | { v: { current: MyStoreType; }}) => 
    {
        const value = "v" in v ? v.v.current : v;

        if (selector === undefined)
        {
            return value;
        }
        else
        {
            let selection = { };

            if (typeof selector === "function")
            {
                selection = selector(value);
            }
            else if (Array.isArray(selector))
            {
                for (const key of selector)
                {
                    selection[key] = value[key];
                }
            }

            return {
                ...selection,
                Update: /* Some additional stuff */
            }
        }

    }, [selector]);

    const value = useContextSelector(context, selectFunc);
    return value;
}

However when I pass an array to this hook, a infinite-loop exception will be thrown.

const editor = useMyStoreContext(["currentView", "currentColor"])

Can the use above be implemented in this way, or another way? Thanks for ur time!

NotYoojun commented 2 weeks ago

I think this might be related to #19 (i guess?)

dai-shi commented 2 weeks ago

Yes. The current solution is to use two hooks or use memoization library.

(Zustand's useShallow may work too.)