Skip to content

Generic/typed UserInfo #136

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

Merged
merged 1 commit into from
Jun 21, 2024
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
4 changes: 2 additions & 2 deletions packages/core/src/SDKCore/SDKCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class SDKCore {
window.location.assign(this.urlHelper.getAccountManagementUrl());
}

async fetchUserInfo() {
async fetchUserInfo<T = UserInfo>() {
const userInfoResponse = await fetch(this.urlHelper.getMeUrl(), {
credentials: 'include',
});
Expand All @@ -57,7 +57,7 @@ export class SDKCore {
);
}

const userInfo: UserInfo = await userInfoResponse.json();
const userInfo: T = await userInfoResponse.json();
return userInfo;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/sdk-react/src/components/providers/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ export type UserInfo = {
};

export const FusionAuthContext =
React.createContext<FusionAuthProviderContext>(defaultContext);
React.createContext<FusionAuthProviderContext<UserInfo | any>>(
defaultContext,
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
} from '@fusionauth-sdk/core';
import { TEST_CONFIG } from '#testing-tools/mocks/testConfig';

function renderWithWrapper(config: FusionAuthProviderConfig) {
return renderHook(() => useFusionAuth(), {
function renderWithWrapper<T = UserInfo>(config: FusionAuthProviderConfig) {
return renderHook(() => useFusionAuth<T>(), {
wrapper: ({ children }: PropsWithChildren) => (
<FusionAuthProvider {...config}>{children}</FusionAuthProvider>
<FusionAuthProvider<T> {...config}>{children}</FusionAuthProvider>
),
});
}
Expand Down Expand Up @@ -111,14 +111,17 @@ describe('FusionAuthProvider', () => {
});

test('Will fetch userInfo', async () => {
const user: UserInfo = { given_name: 'Mr. Userton' };
const user = {
name: 'Mr. Userton',
age: 501,
};
const mockUserInfoResponse = {
ok: true,
json: () => Promise.resolve(user),
} as Response;
vi.spyOn(global, 'fetch').mockResolvedValueOnce(mockUserInfoResponse);

const { result } = renderWithWrapper(TEST_CONFIG);
const { result } = renderWithWrapper<typeof user>(TEST_CONFIG);

expect(fetch).not.toHaveBeenCalled();

Expand All @@ -131,7 +134,8 @@ describe('FusionAuthProvider', () => {

await waitFor(() => {
expect(result.current.isFetchingUserInfo).toBe(false);
expect(result.current.userInfo).toBe(user);
expect(result.current.userInfo?.age).toBe(501);
expect(result.current.userInfo?.name).toBe('Mr. Userton');
});
});

Expand Down
28 changes: 13 additions & 15 deletions packages/sdk-react/src/components/providers/FusionAuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropsWithChildren, useContext, useMemo, useState, FC } from 'react';
import { PropsWithChildren, useContext, useMemo, useState } from 'react';

import { SDKCore } from '@fusionauth-sdk/core';

Expand All @@ -9,17 +9,13 @@ import {
useUserInfo,
useCookieAdapter,
} from './hooks';
import { FusionAuthContext } from './Context';
import { FusionAuthContext, UserInfo as DefaultUserInfo } from './Context';
import { FusionAuthProviderContext } from './FusionAuthProviderContext';

const FusionAuthProvider: FC<
FusionAuthProviderConfig & PropsWithChildren
> = props => {
const config = useMemo<FusionAuthProviderConfig>(() => {
const { children, ...config } = props;
return config;
}, [props]);

function FusionAuthProvider<T = DefaultUserInfo>({
children,
...config
}: PropsWithChildren & FusionAuthProviderConfig) {
const cookieAdapter = useCookieAdapter(config);

const core: SDKCore = useMemo<SDKCore>(() => {
Expand All @@ -35,7 +31,7 @@ const FusionAuthProvider: FC<
const { manageAccount, startLogin, startLogout, startRegister } =
useRedirecting(core, config.onRedirect);

const { isFetchingUserInfo, userInfo, fetchUserInfo, error } = useUserInfo(
const { isFetchingUserInfo, userInfo, fetchUserInfo, error } = useUserInfo<T>(
core,
config.shouldAutoFetchUserInfo ?? false,
);
Expand All @@ -45,7 +41,7 @@ const FusionAuthProvider: FC<
config.shouldAutoRefresh ?? false,
);

const providerValue: FusionAuthProviderContext = {
const providerValue: FusionAuthProviderContext<T> = {
startLogin,
startRegister,
startLogout,
Expand All @@ -61,14 +57,16 @@ const FusionAuthProvider: FC<

return (
<FusionAuthContext.Provider value={providerValue}>
{props.children}
{children}
</FusionAuthContext.Provider>
);
};
}

/**
* A hook that returns `FusionAuthProviderContext`
*/
const useFusionAuth = () => useContext(FusionAuthContext);
function useFusionAuth<T = DefaultUserInfo>() {
return useContext<FusionAuthProviderContext<T>>(FusionAuthContext);
}

export { FusionAuthProvider, useFusionAuth };
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UserInfo } from './Context';

/** The context provided by FusionAuth React SDK */
export interface FusionAuthProviderContext {
export interface FusionAuthProviderContext<T = UserInfo> {
/**
* Whether the user is logged in.
*/
Expand All @@ -10,14 +10,13 @@ export interface FusionAuthProviderContext {
/**
* Data fetched from the configured 'me' endpoint.
*/
userInfo: UserInfo | null;
userInfo: T | null;

/**
* Fetches user info from the 'me' endpoint.
* This is handled automatically if the SDK is configured with `shouldAutoFetchUserInfo`.
* @returns {Promise<UserInfo>}
*/
fetchUserInfo: () => Promise<UserInfo | undefined>;
fetchUserInfo: () => Promise<T | undefined>;

/**
* Indicates that the fetchUserInfo call is unresolved.
Expand Down
11 changes: 7 additions & 4 deletions packages/sdk-react/src/components/providers/hooks/useUserInfo.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useState, useCallback, useRef, useEffect } from 'react';

import { SDKCore, UserInfo } from '@fusionauth-sdk/core';
import { SDKCore } from '@fusionauth-sdk/core';

export function useUserInfo(core: SDKCore, shouldAutoFetchUserInfo: boolean) {
export function useUserInfo<T>(
core: SDKCore,
shouldAutoFetchUserInfo: boolean,
) {
const [isFetchingUserInfo, setIsFetchingUserInfo] = useState(false);
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const [userInfo, setUserInfo] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);

const fetchUserInfo = useCallback(async () => {
setIsFetchingUserInfo(true);
setError(null);

try {
const userInfo = await core.fetchUserInfo();
const userInfo = await core.fetchUserInfo<T>();
setUserInfo(userInfo);
return userInfo;
} catch (error) {
Expand Down