useContext

March 19th, 2024

https://github.com/audiolion/react-auth-provider

I updated my understanding of React useContext.

https://github.com/robert-s-hogan/react-auth-provider-mock

I decided to update and create a default repo that I can use in the future.

September 27, 2023
import React, { createContext, useContext, useState, useEffect } from 'react';
import { CommonProps } from '@with-nx/types';

import { BASE_STATS, CLASS_MODIFIERS, SPECIES_MODIFIERS } from '../data';
import { computeTotalStats } from '../utils';
import { ClassName, Entity, SpeciesName, Stats } from '../types';

const EntityContext = createContext(null);

export const useEntity = () => {
const context = useContext(EntityContext);
if (!context) {
throw new Error('useEntity must be used within an EntityProvider');
}
return context;
};

export const EntityProvider: React.FC<CommonProps> = ({ children }) => {
const [entity, setEntity] = useState<Entity | null>(null);
const [isBattleMode, setIsBattleMode] = useState(false);

// Sync state with local storage
useEffect(() => {
const storedEntity = localStorage.getItem('entity');
if (storedEntity) {
const parsedEntity = JSON.parse(storedEntity);
console.log('Fetched entity from local storage:', parsedEntity);
setEntity(parsedEntity);
}
}, []);

useEffect(() => {
if (entity) {
localStorage.setItem('entity', JSON.stringify(entity));
}
}, [entity]);

const createEntity = (
name: string,
selectedClass: ClassName,
selectedSpecies: SpeciesName,
previewImage: string,
difficulty: number = 1
) => {
localStorage.clear();

const classModifiers = CLASS_MODIFIERS[selectedClass];
const speciesModifiers = SPECIES_MODIFIERS[selectedSpecies];

const stats = computeTotalStats(
BASE_STATS,
classModifiers,
speciesModifiers,
difficulty
);
console.log('Entity being set: ', {
name,
stats,
classType: selectedClass,
species: selectedSpecies,
previewImage,
});

setEntity({
name,
stats,
classType: selectedClass,
species: selectedSpecies,
previewImage,
});
};

const updateEntityImage = (newImage: string) => {
if (entity) {
setEntity({ ...entity, image: newImage });
}
};

const updateEntity = (newEntity: Partial<Entity>) => {
if (entity) {
setEntity({ ...entity, ...newEntity });
}
};

return (
<EntityContext.Provider
value={{
entity,
createEntity,
updateEntity,
updateEntityImage,
isBattleMode,
setIsBattleMode,
}}
>
{children}
</EntityContext.Provider>
);
};
March 1, 2023 (estimate)

The following code was used in a previous project.

import * as React from "react";
import { createContext, useContext, useEffect, useState } from "react";
import { BASE_URL, STORAGE_KEY } from "./utils";
import useCookie from "./useCookie";

interface Session {
token: string;
user: {
id: string;
email: string;
name: string;
organization: string;
};
choreo_key?: {
token: string;
timestamp: string;
slug: string;
};
}

interface CurrentSession {
account: {
email: string;
type: string;
name: string;
organization: string;
subscription: {
tier: string;
start_date: string;
current_period_end: string;
current_period_start: string;
auto_renew: boolean;
plan_interval: string;
storage_limit_in_gb: number;
storage_usage_in_gb: number;
};
};
}

type AuthState = {
status: "uninitialized" | "unauthenticated" | "authenticated" | "authorized";
session?: Session;
currentSession?: CurrentSession;
};

type Auth = {
authState: AuthState;
isLoading: boolean;
currentSessionState: CurrentSession | null;
login: (session: Session) => void;
logout: () => void;
session?: {
token: string;
} | null;
};

type UseCookieConfiguration = {
development?: boolean;
host?: boolean;
};

const AuthContext = createContext<Auth | undefined>(undefined);

export function AuthProvider({
children,
configuration,
}: {
children: React.ReactNode;
configuration: UseCookieConfiguration;
}) {
const [authState, setAuthState] = useState<AuthState>({
status: "uninitialized",
});
const [currentSessionState, setCurrentSessionState] =
useState<CurrentSession | null>(null);

const [isLoading, setIsLoading] = useState(true);
const [getCookie, setCookie] = useCookie(STORAGE_KEY, configuration);

const login = (session: Session) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
if (session.choreo_key) {
setAuthState({
status: "authorized",
session,
});
setIsLoading(false);
} else {
setAuthState({
status: "authenticated",
session,
});
setIsLoading(false);
}
};

const logout = () => {
localStorage.removeItem(STORAGE_KEY);
setAuthState({
status: "unauthenticated",
});
};

useEffect(() => {
const localSession = localStorage.getItem(STORAGE_KEY);

if (localSession) {
const _parsedLocalSession = JSON.parse(localSession);

const parsedLocalSession = (() => {
if (configuration.host) {
if (_parsedLocalSession.token) {
return _parsedLocalSession;
} else {
const _data = _parsedLocalSession?.item?.data;
return typeof _data === "string" ? JSON.parse(_data) : _data;
}
} else {
return _parsedLocalSession;
}
})();

// console.log({ session: "SESSION", parsedLocalSession });

getSession(parsedLocalSession.token)
.then((session) => {
console.log("🔒 SESSION", "[LOGIN]");
login(session);
})
.catch(() => {
console.log("🔒 SESSION", "[LOGOUT]");
logout();
});
} else {
console.log("🔒 SESSION", "[NO LOCAL SESSION]");
logout();
}
}, []);

useEffect(() => {
if (
getCookie &&
getCookie !== "" &&
getCookie !== null &&
getCookie !== undefined
) {
const session: Session = getCookie as Session;

if (authState.session?.token) {
if (session.token !== authState.session?.token) {
getSession(session.token)
.then((_session) => {
setCookie(_session);
login(_session);
})
.catch(() => {
logout();
});
}
} else {
getSession(session.token)
.then((_session) => {
setCookie(_session);
login(_session);
})
.catch(() => {
logout();
});
}
}
}, [getCookie]);

useEffect(() => {
if (authState?.session) {
const localSession = localStorage.getItem(STORAGE_KEY);
if (localSession) {
const _parsedLocalSession = JSON.parse(localSession);

const parsedLocalSession = (() => {
if (configuration.host) {
if (_parsedLocalSession.token) {
return _parsedLocalSession;
} else {
const _data = _parsedLocalSession?.item?.data;
return typeof _data === "string" ? JSON.parse(_data) : _data;
}
} else {
return _parsedLocalSession;
}
})();

getCurrentSession(parsedLocalSession.token).then((session) => {
setCookie(parsedLocalSession);
setCurrentSessionState(session);
});
}
}
}, [authState]);

return (
<AuthContext.Provider
value={{ authState, currentSessionState, login, logout, isLoading }}
>
{children}
</AuthContext.Provider>
);
}

export function useAuth() {
const context = useContext(AuthContext);

if (!context)
throw new Error("useAuth must be used within an `AuthProvider`");

return context;
}

export async function createSession(email: string, password: string) {
const url = new URL("session", BASE_URL);
const body = JSON.stringify({ session: { email, password } });
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body,
});
const parsed = await response.json();
// if (typeof window !== 'undefined') console.log('createSession', parsed);
if (!parsed || !parsed.success || !parsed.data) throw new Error();
return parsed.data as Session;
}

export async function createSessionByKey(choreo_key: string) {
const url = new URL("session", BASE_URL);
const body = JSON.stringify({ session: { choreo_key } });
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body,
});
const parsed = await response.json();
if (typeof window !== "undefined") console.log("createSession", parsed);
if (!parsed || !parsed.success || !parsed.data) throw new Error();
return parsed.data as Session;
}

export async function getSession(token: string) {
const url = new URL("session", BASE_URL);
const response = await fetch(url.toString(), {
method: "GET",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
});
const parsed = await response.json();
// if (typeof window !== 'undefined') console.log('getSession', parsed);
if (!parsed || !parsed.success || !parsed.data) throw new Error();
return parsed.data as Session;
}

export async function getCurrentSession(token: string) {
const url = new URL("account", BASE_URL);
const response = await fetch(url.toString(), {
method: "GET",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
});
const parsed = await response.json();
if (typeof window !== "undefined") console.log("getCurrentSession", parsed);
// if (!parsed || !parsed.success || !parsed.data) throw new Error();
return parsed.data as CurrentSession;
}

export async function resetPassword(email: string) {
const url = new URL("session/reset_password", BASE_URL);
const body = JSON.stringify({ session: { email } });
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body,
});
const parsed = await response.json();
if (typeof window !== "undefined") console.log("resetPassword", parsed);
if (!parsed || !parsed.success) throw new Error();
}

export async function editAccount(
name: string,
email: string,
organization: string,
token: string
): Promise<{ success: boolean; error?: string }> {
const url = new URL("account", BASE_URL);
const body = JSON.stringify({ session: { email } });
const response = await fetch(url.toString(), {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
Accept: "/",
},
body: JSON.stringify({ account: { name, email, organization } }),
});
const parsed = await response.json();
if (!parsed) throw new Error();

if (parsed.errors) {
if (parsed.errors.length) {
return { success: false, error: parsed.errors[0].details };
}
}
return { success: true };
}

export async function createAccount(
name: string,
email: string,
password: string
): Promise<{ success: boolean; error?: string }> {
const url = new URL("account", BASE_URL);
const body = JSON.stringify({ session: { email } });
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "/",
},
body: JSON.stringify({ account: { name, email, password } }),
});
const parsed = await response.json();
if (!parsed) throw new Error();

if (parsed.errors) {
if (parsed.errors.length) {
return { success: false, error: parsed.errors[0].details };
}
}
return { success: true };
}

export async function sendAccessEmails(
slug: string,
emails: string[],
token: string
) {
const url = new URL("session/send_access_emails", BASE_URL);
const body = JSON.stringify({ slug, emails });
const response = await fetch(url.toString(), {
method: "POST",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body,
});
const parsed = await response.json();
if (typeof window !== "undefined") console.log("sendAccessEmails", parsed);
if (!parsed || !parsed.success) throw new Error();
}

The following code was used in a previous project.

The team I was working with decided to put most Auth related calls into a single file. To be fair, this was likely pre-production and before code splitting.