{"id":20849848,"url":"https://github.com/garbles/react-facade","last_synced_at":"2025-08-09T02:06:07.893Z","repository":{"id":41142311,"uuid":"382790569","full_name":"garbles/react-facade","owner":"garbles","description":"Experimental dependency injection for React hooks","archived":false,"fork":false,"pushed_at":"2023-03-12T17:17:32.000Z","size":413,"stargazers_count":111,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-01T08:13:52.803Z","etag":null,"topics":["hooks","proxy","react","react-hooks","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/garbles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-04T07:28:31.000Z","updated_at":"2025-03-26T08:20:41.000Z","dependencies_parsed_at":"2024-11-18T08:39:24.424Z","dependency_job_id":null,"html_url":"https://github.com/garbles/react-facade","commit_stats":{"total_commits":35,"total_committers":2,"mean_commits":17.5,"dds":0.2857142857142857,"last_synced_commit":"2aeadc9694f74c088e293ee631eeada0841e92a5"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Freact-facade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Freact-facade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Freact-facade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Freact-facade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/garbles","download_url":"https://codeload.github.com/garbles/react-facade/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252883219,"owners_count":21819157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","proxy","react","react-hooks","typescript"],"created_at":"2024-11-18T03:07:04.160Z","updated_at":"2025-05-07T13:01:21.513Z","avatar_url":"https://github.com/garbles.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Facade\n\nWrite components with placeholders for hooks.\n\nAn experimental 2kb library that uses [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and TypeScript to create a strongly typed [facade](https://en.wikipedia.org/wiki/Facade_pattern) for your React hooks.\n\n- **Dependency inversion between components and hooks:** Build components that rely on hooks that do not have a particular implementation.\n- **Works with all of your existing hooks:** Extract hooks to the top level of your program and replace them with a facade.\n- **Simplified component testing:** You were already testing your hooks anyway (right?), so why test them again? Focus on rendering outcomes rather than bungling around with a cumbersome setup for test cases.\n\nThe hooks implementation is injected via React context so that they can be changed between views or testing. It is dependency injection for hooks that does not require higher-order functions/Components.\n\n## Example\n\nConsider an application which describes its data-fetching layer in the UI with the following list of hooks,\n\n```ts\nfunction useCurrentUser(): User;\nfunction usePostById(id: string): { loading: boolean; error?: Error; data?: Post };\nfunction useCreateNewPost(): (postData: PostData) =\u003e Promise\u003cPost\u003e;\n// etc...\n```\n\nGiven this interface, a developer can reliably use these hooks without knowing anything about their underlying _implementation_ (leaving aside questions of fidelity). That is to say: the developer _only_ cares about the _interface_. The problem, however, is that by inlining a hook as part of the React component, the _implementation_ cannot be ignored. For example, a component `UserProfile` may have the following definition,\n\n```ts\n// user-profile.tsx\n\nimport React from \"react\";\nimport { userCurrentUser } from \"./hooks\";\n\nexport function UserProfile() {\n  const user = useCurrentUser();\n\n  // ... render user profile\n}\n```\n\nThe developer of this component may not care about the implementation of `useCurrentUser`, but the tests sure do! If under the hood `useCurrentUser` calls the react-redux `useSelector`, then `UserProfile` depends directly on a global Redux store. Moreover, any component using `UserProfile` also has this dependency. The coupling between the store and component tree is hard-coded by this hook. Yikes!\n\nConsider the same problem where the _implementation_ can be completely ignored and replaced by dependency injection. We define the same interface using `createFacade`,\n\n```ts\n// facade.ts\n\nimport { createFacade } from \"react-facade\";\n\ntype Hooks = {\n  useCurrentUser(): User;\n  usePostById(id: string): { loading?: boolean; error?: Error; data?: Post };\n  useCreateNewPost(): (postData: PostData) =\u003e Promise\u003cPost\u003e;\n  // ...\n};\n\n// no implementation!\nexport const [hooks, ImplementationProvider] = createFacade\u003cHooks\u003e();\n```\n\nAnd then the `UserProfile` becomes,\n\n```ts\n// user-profile.tsx\n\nimport React from \"react\";\nimport { hooks } from \"./facade\";\n\nexport function UserProfile() {\n  const user = hooks.useCurrentUser();\n\n  // ... render user profile\n}\n```\n\nThis time, we don't care about the _implementation_ because there literally isn't one. Depending on the environment, it can be injected by passing a different _implementation_ to `ImplementationProvider`.\n\nAt the application level, we might use `useSelector` to fetch the current user from our store,\n\n```tsx\n// app.tsx\n\nimport React from \"react\";\nimport { useSelector } from \"react-redux\";\nimport { ImplementationProvider } from \"./facade\";\n// ...\n\nconst implementation = {\n  useCurrentUser(): User {\n    return useSelector(getCurrentUser);\n  },\n\n  // ...\n};\n\nreturn (\n  \u003cImplementationProvider implementation={implementation}\u003e\n    \u003cUserProfile /\u003e\n  \u003c/ImplementationProvider\u003e\n);\n```\n\nWhile in a test environment, we can return a stub user so long as it matches our _interface_,\n\n```tsx\n// user-profile.test.tsx\n\nimport React from \"react\";\nimport { render } from \"@testing-library/react\";\nimport { ImplementationProvider } from \"./facade\";\n// ...\n\ntest(\"some thing\", () =\u003e {\n  const implementation = {\n    useCurrentUser(): User {\n      return {\n        id: \"stub\",\n        name: \"Gabe\",\n        // ...\n      };\n    },\n\n    // ...\n  };\n\n  const result = render(\n    // What is `__UNSAFE_Test_Partial`? See API section\n    \u003cImplementationProvider.__UNSAFE_Test_Partial implementation={implementation}\u003e\n      \u003cUserProfile /\u003e\n    \u003c/ImplementationProvider.__UNSAFE_Test_Partial\u003e\n  );\n\n  // ...\n});\n```\n\nWe are programming purely toward the _interface_ and _NOT_ the _implementation_!\n\nAgain, consider how this might simplify testing a component that relied on this hook,\n\n```ts\nfunction usePostById(id: string): { loading: boolean; error?: Error; data?: Post };\n```\n\nTesting different states is simply a matter of declaratively passing in the right one,\n\n```ts\n// post.test.tsx\n\nconst loadingImplementation = {\n  usePostById(id: string) {\n    return {\n      loading: true,\n    };\n  },\n};\n\nconst errorImplementation = {\n  usePostById(id: string) {\n    return {\n      loading: false,\n      error: new Error(\"uh oh!\"),\n    };\n  },\n};\n\n// ...\n\ntest(\"shows the loading spinner\", () =\u003e {\n  const result = render(\n    \u003cImplementationProvider.__UNSAFE_Test_Partial implementation={loadingImplementation}\u003e\n      \u003cPost id={id} /\u003e\n    \u003c/ImplementationProvider.__UNSAFE_Test_Partial\u003e\n  );\n\n  // ...\n});\n\ntest(\"displays an error\", () =\u003e {\n  const result = render(\n    \u003cImplementationProvider.__UNSAFE_Test_Partial implementation={errorImplementation}\u003e\n      \u003cPost id={id} /\u003e\n    \u003c/ImplementationProvider.__UNSAFE_Test_Partial\u003e\n  );\n\n  // ...\n});\n```\n\n## API\n\n### `createFacade`\n\n```ts\ntype Wrapper = React.JSXElementConstructor\u003c{ children: React.ReactElement }\u003e;\n\nfunction createFacade\u003cT\u003e(\n  options?: Partial\u003c{ displayName: string; strict: boolean; wrapper: Wrapper }\u003e\n): [Proxy\u003cT\u003e, ImplementationProvider\u003cT\u003e];\n```\n\nTakes a type definition `T` - which must be an object where each member is a function - and returns the tuple of the interface `T` (via a Proxy) and an `ImplementationProvider`. The developer provides the real implementation of the interface through the Provider.\n\nThe `ImplementationProvider` does not collide with other `ImplementationProvider`s created by other `createFacade` calls, so you can make as many of these as you need.\n\n#### Options\n\n| option      | type     | default                    | details                                                                  |\n| ----------- | -------- | -------------------------- | ------------------------------------------------------------------------ |\n| displayName | string   | \"Facade\"                   | The displayName for debugging with React Devtools .                      |\n| strict      | boolean  | true                       | When `true` does not allow the implementation to change between renders. |\n| wrapper     | React.FC | ({ children }) =\u003e children | A wrapper component that can be used to wrap the ImplementationProvider. |\n\n### `ImplementationProvider\u003cT\u003e`\n\nAccepts a prop `implementation: T` that implements the interface defined in `createFacade\u003cT\u003e()`.\n\n```ts\nconst implementation = {\n  useCurrentUser(): User {\n    return useSelector(getCurrentUser);\n  },\n\n  // ...\n};\n\nreturn (\n  \u003cImplementationProvider implementation={implementation}\u003e\n    \u003cUserProfile /\u003e\n  \u003c/ImplementationProvider\u003e\n);\n```\n\n### `ImplementationProvider\u003cT\u003e.__UNSAFE_Test_Partial`\n\nUsed for partially implementing the interface when you don't need to implement the whole thing but still want it to type-check (tests?). For the love of God, please do not use this outside of tests...\n\n```tsx\n\u003cImplementationProvider.__UNSAFE_Test_Partial implementation={partialImplementation}\u003e\n  \u003cUserProfile /\u003e\n\u003c/ImplementationProvider.__UNSAFE_Test_Partial\u003e\n```\n\n## Installing\n\n```\nnpm install react-facade\n```\n\n## Asked Questions\n\n### Why not just use `jest.mock?`\n\nMocking at the module level has the notable downside that type safety is optional. The onus is on the developer to ensure that the mock matches the actual interface. While stubbing with a _static_ language is dangerous enough because it removes critical interactions between units of code, a _dynamic_ language is even worse because changes to the real implementation interface (without modifications to the stub) can result in runtime type errors in production. Choosing to forgo the type check means that you might as well be writing JavaScript.\n\n### Can I use this with plain JavaScript?\n\nIt is _really_ important that this library is used with TypeScript. It's a trick to use a Proxy object in place of the real implementation when calling `createFacade`, so nothing stops you from calling a function that does not exist. Especially bad would be destructuring so your fake hook could be used elsewhere in the program.\n\n```js\n// hooks.js\n\nexport const { useSomethingThatDoesNotExist } = hooks;\n```\n\n```js\n// my-component.jsx\n\nimport { useSomethingThatDoesNotExist } from \"./hooks\";\n\nconst MyComponent = () =\u003e {\n  const value = useSomethingThatDoesNotExist(); // throw new Error('oopsie-doodle!')\n};\n```\n\nThe only thing preventing you from cheating like this is good ol' TypeScript.\n\n### Is this safe to use?\n\nPopular libraries like [`immer`](https://github.com/immerjs/immer) use the same trick of wrapping data `T` in a `Proxy` and present it as `T`, so I don't think you should be concerned. Proxy has [~95% browser support](https://caniuse.com/proxy).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbles%2Freact-facade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgarbles%2Freact-facade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbles%2Freact-facade/lists"}