diff --git a/README.md b/README.md index 6bd71bc..3afe85b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Authenticate your react applications easily with react-query. ## Introduction -Thanks to react-query we have been able to reduce our codebases by a lot by caching server state with it. However, we still have to think about where to store the user data. The user data can be considered as a global application state because we need to access it from lots of places in the application. On the other hand, it is also a server state since all the user data is expected to arrive from a server. With this library, we can manage user authentication in an easy way. It is agnostic of the method you are using for authenticating your application, it can be adjusted according to the API it is being used against. It just needs the configuration to be provided and the rest will be set up automatically. +Thanks to react-query we have been able to reduce our codebases by a lot by caching server state with it. However, we still have to think about where to store the authenticatables (typically a "user") data. The authenticatables data can be considered as a global application state because we need to access it from lots of places in the application. On the other hand, it is also a server state since all the authenticatable data is expected to arrive from a server. With this library, we can manage authentication in an easy way. It is agnostic of the method you are using for authenticating your application, it can be adjusted according to the API it is being used against. It just needs the configuration to be provided and the rest will be set up automatically. ## Table of Contents @@ -52,9 +52,9 @@ First of all, `AuthProvider` and `useAuth` must be initialized and exported. // src/lib/auth.ts import { initReactQueryAuth } from 'react-query-auth'; -import { loginUser, loginFn, registerFn, logoutFn } from '...'; +import { loginAuthenticatable, loginFn, registerFn, logoutFn } from '...'; -interface User { +interface Authenticatable { id: string; name: string; } @@ -64,14 +64,14 @@ interface Error { } const authConfig = { - loadUser, + loadAuthenticatable, loginFn, registerFn, logoutFn, }; export const { AuthProvider, useAuth } = initReactQueryAuth< - User, + Authenticatable, Error, LoginCredentials, RegisterCredentials @@ -100,14 +100,15 @@ export const App = () => { }; ``` -Then the user data is accessible from any component rendered inside the provider via the `useAuth` hook: +Then the authenticatables data is accessible from any component rendered inside the provider via the `useAuth` hook: ```ts // src/components/UserInfo.tsx import { useAuth } from 'src/lib/auth'; export const UserInfo = () => { - const { user } = useAuth(); + // PROTIP: You can use destructuring to name your authenticatable to something that better serves your app + const { authenticatable: user } = useAuth(); return
My Name is {user.name}
; }; ``` @@ -120,9 +121,12 @@ Function that initializes and returns `AuthProvider`, `AuthConsumer` and `useAut ```ts // src/lib/auth.ts -export const { AuthProvider, useAuth } = initReactQueryAuth({ +export const { AuthProvider, useAuth } = initReactQueryAuth< + Authenticatable, + Error +>({ key, - loadUser, + loadAuthenticatable, loginFn, registerFn, logoutFn, @@ -137,36 +141,36 @@ export const { AuthProvider, useAuth } = initReactQueryAuth({ - `key: string` - key that is being used by react-query. - - defaults to `'auth-user'` + - defaults to `'auth-authenticatable'` -- `loadUser: (data:any) => Promise` +- `loadAuthenticatable: (data:any) => Promise` - **Required** - - function that handles user profile fetching + - function that handles authenticatable profile fetching -- `loginFn: (data:any) => Promise` +- `loginFn: (data:any) => Promise` - **Required** - - function that handles user login + - function that handles authenticatable login -- `registerFn: (data:any) => Promise` +- `registerFn: (data:any) => Promise` - **Required** - - function that handles user registration + - function that handles authenticatable registration - `logoutFn: (data:unknown) => Promise` - **Required** - - function that handles user logout + - function that handles authenticatable logout - `logoutFn: () => Promise` - **Required** - - function that handles user logout + - function that handles authenticatable logout - `waitInitial: boolean` - - Flag for checking if the provider should show `LoaderComponent` while fetching the user. If set to `false` it will fetch the user in the background. + - Flag for checking if the provider should show `LoaderComponent` while fetching the authenticatable. If set to `false` it will fetch the authenticatable in the background. - defaults to `true` - `LoaderComponent: () => React.ReactNode` @@ -199,35 +203,42 @@ export const App = () => { #### `useAuth` -The hook allows access of the user data across the app. +The hook allows access of the authenticatable data across the app. ```ts import { useAuth } from 'src/lib/auth'; export const UserInfo = () => { - const { user, login, logout, register, error, refetch } = useAuth(); + const { + authenticatable: user, + login, + logout, + register, + error, + refetch, + } = useAuth(); return
My Name is {user.name}
; }; ``` ##### returns context value: -- `user: User | undefined` +- `authenticatable: Authenticatable | undefined` - - user data that was retrieved from server + - authenticatables data that was retrieved from server - type can be provided by passing it to `initReactQueryAuth` generic - `login: (variables: TVariables, { onSuccess, onSettled, onError }) => Promise` - - function to login the user + - function to login the authenticatable - `logout: (variables: TVariables, { onSuccess, onSettled, onError }) => Promise` - - function to logout the user + - function to logout the authenticatable - `register: (variables: TVariables, { onSuccess, onSettled, onError }) => Promise` - - function to register the user + - function to register the authenticatable - `isLoggingIn: boolean` @@ -241,9 +252,9 @@ export const UserInfo = () => { - checks if is registering in is in progress -- `refetchUser: (options?: RefetchOptions | undefined) => Promise>` +- `refetchAuthenticatable: (options?: RefetchOptions | undefined) => Promise>` - - function for refetching user data. this can also be done by invalidating its query by `key` + - function for refetching authenticatables data. this can also be done by invalidating its query by `key` - `error: Error | null` - error object @@ -262,7 +273,11 @@ export const App = () => { - {({ user }) =>
{JSON.stringify(user) || 'No User Found'}
} + {({ authenticatable }) => ( +
+ {JSON.stringify(authenticatable) || 'No Authenticatable Found'} +
+ )}
diff --git a/sample-app/App.tsx b/sample-app/App.tsx index 7105a12..ccd5c9b 100644 --- a/sample-app/App.tsx +++ b/sample-app/App.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Auth } from './components/Auth'; -import { UserInfo } from './components/UserInfo'; +import { AuthenticatableInfo } from './components/AuthenticatableInfo'; import { useAuth } from './lib/auth'; export function App() { - const { user } = useAuth(); - return user ? : ; + const { authenticatable } = useAuth(); + return authenticatable ? : ; } diff --git a/sample-app/api.ts b/sample-app/api.ts index 08f91a7..35e3ea2 100644 --- a/sample-app/api.ts +++ b/sample-app/api.ts @@ -1,11 +1,11 @@ import { storage } from './utils'; interface AuthResponse { - user: User; + authenticatable: Authenticatable; jwt: string; } -export interface User { +export interface Authenticatable { id: string; email: string; name?: string; @@ -21,7 +21,7 @@ export async function handleApiResponse(response) { } } -export async function getUserProfile() { +export async function getAuthenticatableProfile() { return await fetch('/auth/me', { headers: { Authorization: storage.getToken(), diff --git a/sample-app/components/AuthenticatableInfo.tsx b/sample-app/components/AuthenticatableInfo.tsx new file mode 100644 index 0000000..19961ad --- /dev/null +++ b/sample-app/components/AuthenticatableInfo.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { useAuth } from '../lib/auth'; + +export function AuthenticatableInfo() { + const { authenticatable, logout } = useAuth(); + return ( +
+ Welcome {authenticatable?.name} + +
+ ); +} diff --git a/sample-app/components/Login.tsx b/sample-app/components/Login.tsx index 22ecc39..2f42b09 100644 --- a/sample-app/components/Login.tsx +++ b/sample-app/components/Login.tsx @@ -13,7 +13,7 @@ export function Login() { onSubmit={async e => { e.preventDefault(); try { - await login(values); + await login(values as LoginCredentials); } catch (err) { setError(err); } diff --git a/sample-app/components/Register.tsx b/sample-app/components/Register.tsx index a12d513..39da1f6 100644 --- a/sample-app/components/Register.tsx +++ b/sample-app/components/Register.tsx @@ -14,7 +14,7 @@ export function Register() { onSubmit={async e => { e.preventDefault(); try { - await register(values); + await register(values as RegisterCredentials); } catch (err) { setError(err); } diff --git a/sample-app/components/UserInfo.tsx b/sample-app/components/UserInfo.tsx deleted file mode 100644 index 39a075c..0000000 --- a/sample-app/components/UserInfo.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useAuth } from '../lib/auth'; - -export function UserInfo() { - const { user, logout } = useAuth(); - return ( -
- Welcome {user.name} - -
- ); -} diff --git a/sample-app/lib/auth.ts b/sample-app/lib/auth.ts index 0a02a74..6000554 100644 --- a/sample-app/lib/auth.ts +++ b/sample-app/lib/auth.ts @@ -1,9 +1,9 @@ import { initReactQueryAuth } from '../../src'; import { - getUserProfile, + getAuthenticatableProfile, registerWithEmailAndPassword, loginWithEmailAndPassword, - User, + Authenticatable, } from '../api'; import { storage } from '../utils'; @@ -18,32 +18,32 @@ export type RegisterCredentials = { password: string; }; -async function handleUserResponse(data) { - const { jwt, user } = data; +async function handleAuthenticatableResponse(data) { + const { jwt, authenticatable } = data; storage.setToken(jwt); - return user; + return authenticatable; } -async function loadUser() { - let user = null; +async function loadAuthenticatable() { + let authenticatable = null; if (storage.getToken()) { - const data = await getUserProfile(); - user = data; + const data = await getAuthenticatableProfile(); + authenticatable = data; } - return user; + return authenticatable; } async function loginFn(data: LoginCredentials) { const response = await loginWithEmailAndPassword(data); - const user = await handleUserResponse(response); - return user; + const authenticatable = await handleAuthenticatableResponse(response); + return authenticatable; } async function registerFn(data: RegisterCredentials) { const response = await registerWithEmailAndPassword(data); - const user = await handleUserResponse(response); - return user; + const authenticatable = await handleAuthenticatableResponse(response); + return authenticatable; } async function logoutFn() { @@ -51,14 +51,14 @@ async function logoutFn() { } const authConfig = { - loadUser, + loadAuthenticatable, loginFn, registerFn, logoutFn, }; const { AuthProvider, AuthConsumer, useAuth } = initReactQueryAuth< - User, + Authenticatable, any, LoginCredentials, RegisterCredentials diff --git a/sample-app/mock/db.ts b/sample-app/mock/db.ts index 2a98a68..7f8248e 100644 --- a/sample-app/mock/db.ts +++ b/sample-app/mock/db.ts @@ -1,21 +1,24 @@ -import { User } from '../api'; +import { Authenticatable } from '../api'; -const users: Record = JSON.parse( - window.localStorage.getItem('db_users') || '{}' +const authenticatables: Record = JSON.parse( + window.localStorage.getItem('db_authenticatables') || '{}' ); const blackListEmails = ['hacker@mail.com', 'asd@mail.com']; -export function setUser(data: User) { +export function setAuthenticatable(data: Authenticatable) { if (data?.email && !blackListEmails.includes(data?.email)) { - users[data.email] = data; - window.localStorage.setItem('db_users', JSON.stringify(users)); + authenticatables[data.email] = data; + window.localStorage.setItem( + 'db_authenticatables', + JSON.stringify(authenticatables) + ); return data; } else { return null; } } -export function getUser(email: string) { - return users[email]; +export function getAuthenticatable(email: string) { + return authenticatables[email]; } diff --git a/sample-app/mock/handlers.ts b/sample-app/mock/handlers.ts index d71981c..2e2bfc1 100644 --- a/sample-app/mock/handlers.ts +++ b/sample-app/mock/handlers.ts @@ -1,13 +1,15 @@ import { rest } from 'msw'; -import { User } from '../api'; -import { getUser, setUser } from './db'; +import { Authenticatable } from '../api'; +import { getAuthenticatable, setAuthenticatable } from './db'; export const handlers = [ rest.get('/auth/me', (req, res, ctx) => { - const user = getUser(req.headers.get('Authorization')); + const authenticatable = getAuthenticatable( + req.headers.get('Authorization') + ); - if (user) { - return res(ctx.delay(1000), ctx.json(user)); + if (authenticatable) { + return res(ctx.delay(1000), ctx.json(authenticatable)); } return res( @@ -17,14 +19,14 @@ export const handlers = [ ); }), rest.post('/auth/login', (req, res, ctx) => { - const parsedBody = JSON.parse(req.body as string) as User; - const user = getUser(parsedBody.email); - if (user) { + const parsedBody = JSON.parse(req.body as string) as Authenticatable; + const authenticatable = getAuthenticatable(parsedBody.email); + if (authenticatable) { return res( ctx.delay(1000), ctx.json({ - jwt: user.email, - user, + jwt: authenticatable.email, + authenticatable, }) ); } else { @@ -36,29 +38,29 @@ export const handlers = [ } }), rest.post('/auth/register', (req, res, ctx) => { - const parsedBody = JSON.parse(req.body as string) as User; - const user = getUser(parsedBody?.email); - if (!user && parsedBody) { - const newUser = setUser(parsedBody); - if (newUser) { + const parsedBody = JSON.parse(req.body as string) as Authenticatable; + const authenticatable = getAuthenticatable(parsedBody?.email); + if (!authenticatable && parsedBody) { + const newAuthenticatable = setAuthenticatable(parsedBody); + if (newAuthenticatable) { return res( ctx.delay(1000), ctx.json({ - jwt: newUser.email, - user: getUser(newUser.email), + jwt: newAuthenticatable.email, + authenticatable: getAuthenticatable(newAuthenticatable.email), }) ); } return res( ctx.delay(1000), ctx.status(403), - ctx.json({ message: 'Forbidden User' }) + ctx.json({ message: 'Forbidden Authenticatable' }) ); } else { return res( ctx.delay(1000), ctx.status(400), - ctx.json({ message: 'The user already exists!' }) + ctx.json({ message: 'The authenticatable already exists!' }) ); } }), diff --git a/src/index.tsx b/src/index.tsx index 9f12547..68e9398 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,11 +8,14 @@ import { RefetchOptions, } from 'react-query'; -export interface AuthProviderConfig { +export interface AuthProviderConfig< + Authenticatable = unknown, + Error = unknown +> { key?: string; - loadUser: (data: any) => Promise; - loginFn: (data: any) => Promise; - registerFn: (data: any) => Promise; + loadAuthenticatable: (data: any) => Promise; + loginFn: (data: any) => Promise; + registerFn: (data: any) => Promise; logoutFn: () => Promise; waitInitial?: boolean; LoaderComponent?: () => JSX.Element; @@ -20,21 +23,21 @@ export interface AuthProviderConfig { } export interface AuthContextValue< - User = unknown, + Authenticatable = unknown, Error = unknown, LoginCredentials = unknown, RegisterCredentials = unknown > { - user: User | undefined; - login: UseMutateAsyncFunction; + authenticatable: Authenticatable | undefined; + login: UseMutateAsyncFunction; logout: UseMutateAsyncFunction; - register: UseMutateAsyncFunction; + register: UseMutateAsyncFunction; isLoggingIn: boolean; isLoggingOut: boolean; isRegistering: boolean; - refetchUser: ( + refetchAuthenticatable: ( options?: RefetchOptions | undefined - ) => Promise>; + ) => Promise>; error: Error | null; } @@ -43,13 +46,13 @@ export interface AuthProviderProps { } export function initReactQueryAuth< - User = unknown, + Authenticatable = unknown, Error = unknown, LoginCredentials = unknown, RegisterCredentials = unknown ->(config: AuthProviderConfig) { +>(config: AuthProviderConfig) { const AuthContext = React.createContext
Loading...
, ErrorComponent = (error: any) => ( @@ -73,34 +76,34 @@ export function initReactQueryAuth< const queryClient = useQueryClient(); const { - data: user, + data: authenticatable, error, status, isLoading, isIdle, isSuccess, refetch, - } = useQuery({ + } = useQuery({ queryKey: key, - queryFn: loadUser, + queryFn: loadAuthenticatable, }); - const setUser = React.useCallback( - (data: User) => queryClient.setQueryData(key, data), + const setAuthenticatable = React.useCallback( + (data: Authenticatable) => queryClient.setQueryData(key, data), [queryClient] ); const loginMutation = useMutation({ mutationFn: loginFn, - onSuccess: user => { - setUser(user); + onSuccess: authenticatable => { + setAuthenticatable(authenticatable); }, }); const registerMutation = useMutation({ mutationFn: registerFn, - onSuccess: user => { - setUser(user); + onSuccess: authenticatable => { + setAuthenticatable(authenticatable); }, }); @@ -113,9 +116,9 @@ export function initReactQueryAuth< const value = React.useMemo( () => ({ - user, + authenticatable, error, - refetchUser: refetch, + refetchAuthenticatable: refetch, login: loginMutation.mutateAsync, isLoggingIn: loginMutation.isLoading, logout: logoutMutation.mutateAsync, @@ -124,7 +127,7 @@ export function initReactQueryAuth< isRegistering: registerMutation.isLoading, }), [ - user, + authenticatable, error, refetch, loginMutation.mutateAsync,