From 56e0643a30b5c56bb348af755ba6051ac4a2ee4e Mon Sep 17 00:00:00 2001 From: Josep M Sobrepere Date: Thu, 21 Jan 2021 14:23:02 +0100 Subject: [PATCH] feat(utils): add contextBinder --- packages/utils/src/contextBinder.test.tsx | 60 +++++++++++++++++++++++ packages/utils/src/contextBinder.ts | 38 ++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 packages/utils/src/contextBinder.test.tsx create mode 100644 packages/utils/src/contextBinder.ts diff --git a/packages/utils/src/contextBinder.test.tsx b/packages/utils/src/contextBinder.test.tsx new file mode 100644 index 0000000..b61aaba --- /dev/null +++ b/packages/utils/src/contextBinder.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from "@testing-library/react" +import React, { createContext, useContext } from "react" +import { of } from "rxjs" +import { contextBinder } from "./index" + +describe("contextBinder", () => { + it("bounds the provided context into the first args of the hook", () => { + const idContext = createContext("id1") + const countContext = createContext(3) + const useId = () => useContext(idContext) + const useCount = () => useContext(countContext) + const idCountBind = contextBinder(useId, useCount) + + const [useSomething] = idCountBind( + (id: string, count: number, append: string) => + of(Array(count).fill(id).concat(append).join("-")), + "", + ) + + const Result: React.FC = () => Result {useSomething("bar")} + + const Component: React.FC<{ id: string; count: number }> = ({ + id, + count, + }) => { + return ( + + + + + + ) + } + + render() + + expect(screen.queryByText("Result foo-foo-foo-foo-bar")).not.toBeNull() + }) + + it("the returned function matches the signature of the original one", () => { + const idContext = createContext("id1") + const countContext = createContext(3) + const useId = () => useContext(idContext) + const useCount = () => useContext(countContext) + const idCountBind = contextBinder(useId, useCount) + + const [, getSomething$] = idCountBind( + (id: string, count: number, append: string) => + of(Array(count).fill(id).concat(append).join("-")), + "", + ) + + let value = "" + getSomething$("foo", 4, "bar").subscribe((v) => { + value = v + }) + + expect(value).toBe("foo-foo-foo-foo-bar") + }) +}) diff --git a/packages/utils/src/contextBinder.ts b/packages/utils/src/contextBinder.ts new file mode 100644 index 0000000..2c0e187 --- /dev/null +++ b/packages/utils/src/contextBinder.ts @@ -0,0 +1,38 @@ +import { Observable } from "rxjs" +import { bind } from "@react-rxjs/core" + +type SubstractTuples = A2 extends [unknown, ...infer Rest2] + ? A1 extends [unknown, ...infer Rest1] + ? SubstractTuples + : [] + : A1 + +const execSelf = (fn: () => T) => fn() + +/** + * Returns a version of bind where its hook will have the first parameters bound + * the results of the provided functions + * + * @param {...React.Context} context - The React.Context that should be bound to the hook. + */ +export function contextBinder< + A extends (() => any)[], + OT extends { + [K in keyof A]: A[K] extends () => infer V ? V : unknown + } +>( + ...args: A +): ( + getObservable: (...args: ARGS) => Observable, + defaultValue?: T | undefined, +) => [ + (...args: SubstractTuples) => T, + (...args: ARGS) => Observable, +] +export function contextBinder(...args: any[]) { + const useArgs = () => args.map(execSelf) + return function () { + const [hook, getter] = bind.apply(null, arguments as any) as any + return [(...args: any[]) => (hook as any)(...useArgs(), ...args), getter] + } as any +}