From a3c4095f8073cf1d358dd93440f20d1ab296a9cf Mon Sep 17 00:00:00 2001 From: Kristian PD Date: Wed, 5 Jul 2023 14:03:47 -0400 Subject: [PATCH] Move useIsSignedIn into a useAuth hook with { user, session, isSignedIn } properties --- packages/react/README.md | 18 ++++--- packages/react/spec/auth/useAuth.spec.ts | 52 +++++++++++++++++++ .../react/spec/auth/useIsSignedIn.spec.ts | 48 ----------------- packages/react/spec/auth/useSession.spec.ts | 28 +++++++--- packages/react/src/auth/useAuth.ts | 12 +++++ packages/react/src/auth/useIsSignedIn.ts | 10 ---- packages/react/src/auth/useSession.ts | 3 +- packages/react/src/index.ts | 2 +- 8 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 packages/react/spec/auth/useAuth.spec.ts delete mode 100644 packages/react/spec/auth/useIsSignedIn.spec.ts create mode 100644 packages/react/src/auth/useAuth.ts delete mode 100644 packages/react/src/auth/useIsSignedIn.ts diff --git a/packages/react/README.md b/packages/react/README.md index 2d2873ccf..2b5eaca2d 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -1078,13 +1078,19 @@ Returns the current session, equivalent to `await api.currentSession.get()` or ` ### `useUser()` Returns the current user of the session, if present. For unauthenticated sessions, returns `null`. Throws a Suspense promise while the session/user are loading -### `useIsSignedIn()` -Returns true if the session is logged in (has a user associated with it), and false otherwise. Throws a Suspense promise while the session/user is loading +### `useAuth()` +Returns an object representing the current authentication state of the session. Throws a Suspense promise while the session is being loaded. + +`user` - the current `User`, if signed in. similar to `useUser` + +`session` - the current `Session`. similar to `useSession` + +`isSignedIn` - set to `true` if the session has a user associated with it (signed in), `false` otherwise ```tsx export default function App() { const user = useUser(); - const isSignedIn = useIsSignedIn(); + const { isSignedIn } = useAuth(); const gadgetContext = useGadgetContext(); return ( @@ -1100,20 +1106,20 @@ export default function App() { If you are trying to control the layout of your application based on authentication state, it may be helpful to use the Gadget auth React components instead of, or in addition to, the hooks. ### `` -Conditionally renders its children if the current session has a user associated with it, similar to the `useIsSignedIn()` hook. +Conditionally renders its children if the current session has a user associated with it, similar to the `isSignedIn` property of the `useAuth()` hook. ```tsx

Hello, human!

``` ### `` -Conditionally renders its children if the current session has a user associated with it, similar to the `useIsSignedIn()` hook. +Conditionally renders its children if the current session has a user associated with it. ```tsx Sign In! ``` -### +### `` Conditionally renders its children if the current session has a user associated with it, or redirects the browser via `window.location.assign` if the user is not currently signed in. This component is helpful for protecting front-end routes. ```tsx diff --git a/packages/react/spec/auth/useAuth.spec.ts b/packages/react/spec/auth/useAuth.spec.ts new file mode 100644 index 000000000..2ed678578 --- /dev/null +++ b/packages/react/spec/auth/useAuth.spec.ts @@ -0,0 +1,52 @@ +import { renderHook } from "@testing-library/react"; +import { useAuth } from "../../src/auth/useAuth"; +import { superAuthApi } from "../apis"; +import { TestWrapper } from "../testWrapper"; +import { expectMockSignedInUser, expectMockSignedOutUser, mockInternalServerError, mockNetworkError } from "../utils"; + +describe("useAuth", () => { + test("returns the correct auth state if the user is signed in", async () => { + const { result, rerender } = renderHook(() => useAuth(), { wrapper: TestWrapper(superAuthApi) }); + + expectMockSignedInUser(); + + rerender(); + expect(result.current.isSignedIn).toBe(true); + expect(result.current.session.id).toBe("123"); + expect(result.current.user!.id).toBe("321"); + }); + + test("returns the correct auth state if the user is signed out", async () => { + const { result, rerender } = renderHook(() => useAuth(), { wrapper: TestWrapper(superAuthApi) }); + + expectMockSignedOutUser(); + + rerender(); + expect(result.current.isSignedIn).toBe(false); + expect(result.current.session.id).toBe("123"); + expect(result.current.user).toBe(null); + }); + + test("it throws when the server responds with an error", async () => { + expect(() => { + const { rerender } = renderHook(() => useAuth(), { wrapper: TestWrapper(superAuthApi) }); + + mockInternalServerError(); + + rerender(); + }).toThrowErrorMatchingInlineSnapshot(` + "[GraphQL] GGT_INTERNAL_SERVER_ERROR + [GraphQL] An error occurred" + `); + }); + + test("it throws when request fails to complete", async () => { + expect(() => { + const { rerender } = renderHook(() => useAuth(), { wrapper: TestWrapper(superAuthApi) }); + + mockNetworkError(); + + rerender(); + }).toThrowErrorMatchingInlineSnapshot(`"[Network] Network error"`); + }); +}); diff --git a/packages/react/spec/auth/useIsSignedIn.spec.ts b/packages/react/spec/auth/useIsSignedIn.spec.ts deleted file mode 100644 index 65fdae047..000000000 --- a/packages/react/spec/auth/useIsSignedIn.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { renderHook } from "@testing-library/react"; -import { superAuthApi } from "../../spec/apis"; -import { expectMockSignedInUser, expectMockSignedOutUser, mockInternalServerError, mockNetworkError } from "../../spec/utils"; -import { useIsSignedIn } from "../../src/auth/useIsSignedIn"; -import { TestWrapper } from "../testWrapper"; - -describe("useIsSignedIn", () => { - test("returns true if the user is signed in", async () => { - const { result, rerender } = renderHook(() => useIsSignedIn(), { wrapper: TestWrapper(superAuthApi) }); - - expectMockSignedInUser(); - - rerender(); - expect(result.current).toBe(true); - }); - - test("returns false if the user is signed out", async () => { - const { result, rerender } = renderHook(() => useIsSignedIn(), { wrapper: TestWrapper(superAuthApi) }); - - expectMockSignedOutUser(); - - rerender(); - expect(result.current).toBe(false); - }); - - test("it throws when the server responds with an error", async () => { - expect(() => { - const { rerender } = renderHook(() => useIsSignedIn(), { wrapper: TestWrapper(superAuthApi) }); - - mockInternalServerError(); - - rerender(); - }).toThrowErrorMatchingInlineSnapshot(` - "[GraphQL] GGT_INTERNAL_SERVER_ERROR - [GraphQL] An error occurred" - `); - }); - - test("it throws when request fails to complete", async () => { - expect(() => { - const { rerender } = renderHook(() => useIsSignedIn(), { wrapper: TestWrapper(superAuthApi) }); - - mockNetworkError(); - - rerender(); - }).toThrowErrorMatchingInlineSnapshot(`"[Network] Network error"`); - }); -}); diff --git a/packages/react/spec/auth/useSession.spec.ts b/packages/react/spec/auth/useSession.spec.ts index 93a650f22..5b8054ffd 100644 --- a/packages/react/spec/auth/useSession.spec.ts +++ b/packages/react/spec/auth/useSession.spec.ts @@ -11,11 +11,11 @@ describe("useSession", () => { expectMockSignedInUser(); rerender(); - expect(result.current!.id).toEqual("123"); - expect(result.current!.userId).toEqual("321"); - expect(result.current!.user!.id).toEqual("321"); - expect(result.current!.user!.firstName).toEqual("Jane"); - expect(result.current!.user!.lastName).toEqual("Doe"); + expect(result.current.id).toEqual("123"); + expect(result.current.userId).toEqual("321"); + expect(result.current.user!.id).toEqual("321"); + expect(result.current.user!.firstName).toEqual("Jane"); + expect(result.current.user!.lastName).toEqual("Doe"); }); test("it returns the current session when the user is logged out", async () => { @@ -25,9 +25,21 @@ describe("useSession", () => { rerender(); expect(result.current).toBeDefined(); - expect(result.current!.id).toEqual("123"); - expect(result.current!.userId).toBe(null); - expect(result.current!.user).toBe(null); + expect(result.current.id).toEqual("123"); + expect(result.current.userId).toBe(null); + expect(result.current.user).toBe(null); + }); + + test("it returns the current session when the user is logged out", async () => { + const { result, rerender } = renderHook(() => useSession(), { wrapper: TestWrapper(superAuthApi) }); + + expectMockSignedOutUser(); + rerender(); + + expect(result.current).toBeDefined(); + expect(result.current.id).toEqual("123"); + expect(result.current.userId).toBe(null); + expect(result.current.user).toBe(null); }); test("it throws when the server responds with an error", async () => { diff --git a/packages/react/src/auth/useAuth.ts b/packages/react/src/auth/useAuth.ts new file mode 100644 index 000000000..3737a4d04 --- /dev/null +++ b/packages/react/src/auth/useAuth.ts @@ -0,0 +1,12 @@ +import { useSession } from "./useSession"; +import { useUser } from "./useUser"; + +/** + * Used for fetching the current authentication state of the session + * @returns An object with the current authentication state: `session`, `user`, and `isSignedIn` + */ +export const useAuth = () => { + const session = useSession(); + const user = useUser(); + return { session, user, isSignedIn: !!user }; +}; diff --git a/packages/react/src/auth/useIsSignedIn.ts b/packages/react/src/auth/useIsSignedIn.ts deleted file mode 100644 index a848a894e..000000000 --- a/packages/react/src/auth/useIsSignedIn.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useUser } from "./useUser"; - -/** - * Used for determining if the current `Session` has a user `User` associated with it (is signed in). Will suspend while the session is being fetched. - * @returns `true` if the `Session` has a `User`, `false` otherwise. - */ -export const useIsSignedIn = () => { - const user = useUser(); - return !!user; -}; diff --git a/packages/react/src/auth/useSession.ts b/packages/react/src/auth/useSession.ts index 5f683be0c..57ddece6f 100644 --- a/packages/react/src/auth/useSession.ts +++ b/packages/react/src/auth/useSession.ts @@ -35,8 +35,9 @@ const useGetSessionAndUser = () => { * Used for fetching the current `Session` record from Gadget. Will suspend while the user is being fetched. * @returns The current session */ -export const useSession = (): GadgetSession | undefined => { +export const useSession = (): GadgetSession => { const [{ data: session, error }] = useGetSessionAndUser(); if (error) throw error; + if (!session) throw new Error("currentSession not found but should be present"); return session; }; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index ac37bf938..05851d334 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,6 +1,6 @@ export { Consumer, Context } from "urql"; export { Provider } from "./GadgetProvider"; -export * from "./auth/useIsSignedIn"; +export * from "./auth/useAuth"; export * from "./auth/useSession"; export * from "./auth/useUser"; export * from "./components/auth/SignedIn";