From 63e1905b00fb83377803b9e81179af1031e1c8f8 Mon Sep 17 00:00:00 2001 From: Harry Brundage Date: Sun, 25 Jun 2023 22:21:48 -0400 Subject: [PATCH] Add a useApi hook for accessing the current api client without having a reference to it --- packages/react/spec/GadgetProvider.spec.tsx | 26 ++++++++++++++- packages/react/src/GadgetProvider.tsx | 37 +++++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/react/spec/GadgetProvider.spec.tsx b/packages/react/spec/GadgetProvider.spec.tsx index f8384461d..ab88658e8 100644 --- a/packages/react/spec/GadgetProvider.spec.tsx +++ b/packages/react/spec/GadgetProvider.spec.tsx @@ -4,7 +4,7 @@ import { GadgetConnection } from "@gadgetinc/api-client-core"; import { renderHook } from "@testing-library/react"; import type { ReactNode } from "react"; import React from "react"; -import { Provider, useConnection } from "../src/GadgetProvider"; +import { Provider, useApi, useConnection } from "../src/GadgetProvider"; import { mockUrqlClient } from "./testWrapper"; describe("GadgetProvider", () => { @@ -37,6 +37,16 @@ describe("GadgetProvider", () => { expect(result).toBeTruthy(); }); + test("internal components can access the api client when wrapped in the provider which is passed an api client", () => { + const { result } = renderHook(() => useApi(), { + wrapper: (props: { children: ReactNode }) => { + return {props.children}; + }, + }); + + expect(result.current).toBe(mockApiClient); + }); + test("the provider errors when not passed anything", () => { expect(() => renderHook(() => useConnection(), { @@ -62,4 +72,18 @@ describe("GadgetProvider", () => { `"Invalid Gadget API client passed to component -- please pass an instance of your generated client, like !"` ); }); + + test("internal components can't access the api client when wrapped in the provider which is passed an urql client", () => { + expect(() => + renderHook(() => useApi(), { + wrapper: (props: { children: ReactNode }) => { + return {props.children}; + }, + }) + ).toThrowErrorMatchingInlineSnapshot(` + "useApi hook called in context with deprecated convention. Please ensure you are wrapping this hook with the component from @gadgetinc/react and passing it an instance of your api client, like . + + The component is currently being passed a value, like . Please update this to ." + `); + }); }); diff --git a/packages/react/src/GadgetProvider.tsx b/packages/react/src/GadgetProvider.tsx index 385656b19..644b149e3 100644 --- a/packages/react/src/GadgetProvider.tsx +++ b/packages/react/src/GadgetProvider.tsx @@ -1,7 +1,6 @@ import type { AnyClient, GadgetConnection } from "@gadgetinc/api-client-core"; import { $gadgetConnection, isGadgetClient } from "@gadgetinc/api-client-core"; -import type { ReactNode } from "react"; -import React from "react"; +import React, { ReactNode, useContext } from "react"; import type { Client as UrqlClient } from "urql"; import { Provider as UrqlProvider } from "urql"; @@ -89,8 +88,12 @@ export function Provider(props: ProviderProps | DeprecatedProviderProps) { ); } +/** + * Get the current `GadgetConnection` object from React context. + * Must be called within a component wrapped by the `` component. + **/ export const useConnection = () => { - const urqlClient = React.useContext(GadgetUrqlClientContext); + const urqlClient = useContext(GadgetUrqlClientContext); if (!urqlClient) { throw new Error("No urql client object in React context, have you added the wrapper component from @gadgetinc/react?"); } @@ -108,3 +111,31 @@ export const useConnection = () => { return connection; }; + +/** + * Get the current `api` object from React context + * Must be called within a component wrapped by the `` component. + **/ +export const useApi = () => { + const api = useContext(GadgetClientContext); + const urqlClient = useContext(GadgetUrqlClientContext); + if (!api) { + if (urqlClient) { + throw new Error( + `useApi hook called in context with deprecated convention. Please ensure you are wrapping this hook with the component from @gadgetinc/react and passing it an instance of your api client, like . + + The component is currently being passed a value, like . Please update this to .` + ); + } else { + throw new Error( + `useApi hook called in context where no Gadget API client is available. Please ensure you are wrapping this hook with the component from @gadgetinc/react. + + Possible remedies: + - ensuring you have the component wrapped around your hook invocation + - ensuring you are passing an api client instance to the provider, usually + - ensuring your @gadget-client/ package and your @gadgetinc/react package are up to date` + ); + } + } + return api; +};