Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a useApi hook for accessing the current api client without having a reference to it #217

Merged
merged 1 commit into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};