Skip to content

Commit

Permalink
Add a useApi hook for accessing the current api client without having…
Browse files Browse the repository at this point in the history
… a reference to it
  • Loading branch information
airhorns committed Jun 26, 2023
1 parent 095e3da commit 63e1905
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 4 deletions.
26 changes: 25 additions & 1 deletion packages/react/spec/GadgetProvider.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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 <Provider api={mockApiClient}>{props.children}</Provider>;
},
});

expect(result.current).toBe(mockApiClient);
});

test("the provider errors when not passed anything", () => {
expect(() =>
renderHook(() => useConnection(), {
Expand All @@ -62,4 +72,18 @@ describe("GadgetProvider", () => {
`"Invalid Gadget API client passed to <Provider /> component -- please pass an instance of your generated client, like <Provider api={api} />!"`
);
});

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 <Provider value={mockUrqlClient}>{props.children}</Provider>;
},
})
).toThrowErrorMatchingInlineSnapshot(`
"useApi hook called in context with deprecated <Provider/> convention. Please ensure you are wrapping this hook with the <Provider/> component from @gadgetinc/react and passing it an instance of your api client, like <Provider api={api} />.
The <Provider /> component is currently being passed a value, like <Provider value={api.connection.currentClient}/>. Please update this to <Provider api={api} />."
`);
});
});
37 changes: 34 additions & 3 deletions packages/react/src/GadgetProvider.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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 `<Provider api={...} />` 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 <Provider/> wrapper component from @gadgetinc/react?");
}
Expand All @@ -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 `<Provider api={...} />` 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 <Provider/> convention. Please ensure you are wrapping this hook with the <Provider/> component from @gadgetinc/react and passing it an instance of your api client, like <Provider api={api} />.
The <Provider /> component is currently being passed a value, like <Provider value={api.connection.currentClient}/>. Please update this to <Provider api={api} />.`
);
} 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 <Provider/> component from @gadgetinc/react.
Possible remedies:
- ensuring you have the <Provider/> component wrapped around your hook invocation
- ensuring you are passing an api client instance to the provider, usually <Provider api={api}>
- ensuring your @gadget-client/<your-app> package and your @gadgetinc/react package are up to date`
);
}
}
return api;
};

0 comments on commit 63e1905

Please sign in to comment.