use Graft Auth
A credential and whether it is there. The shape is yours, and so is persistence, through defaultValue.
API
Options
| prop | type | default | |
|---|---|---|---|
defaultValue? | T | null | null | the token to start from |
Returns
| prop | type | default | |
|---|---|---|---|
token? | T | null | — | the credential, in whatever shape yours has |
isAuthorized? | boolean | — | token !== null |
onAuth? | (token: T) => void | — | stores a token |
onAuthRevoke? | () => void | — | drops it |
id? | string | — | stable id |
Usage
A token, held
// -constants/graft_session.ts
export const GraftSession = createGraft({ name: "GraftSession", graft: useGraftAuth<string> });
// -components/session_controls.tsx
export const SessionControls = () => {
const { isAuthorized, onAuth, onAuthRevoke, token } = GraftSession.use();
return (
<>
<Button onClick={() => (isAuthorized ? onAuthRevoke() : onAuth("jwt-token"))}>
{isAuthorized ? "sign out" : "sign in"}
</Button>
<Chip>{isAuthorized ? `authorized: ${token}` : "anonymous"}</Chip>
</>
);
};Details
A token, whatever shape yours has
T is open because a token is a different thing on every backend — a bare string here, { access, refresh, expiresAt } there. It is not an invitation to keep something else in here: a user, a set of preferences, anything that is not the credential itself belongs in a store, and this hook would only give it a misleading name.
Because what this hook adds over a store is the vocabulary, not the mechanism. Holding a value is the easy part; being called token, onAuth and isAuthorized is what makes a file recognisable at a glance as the one that decides who you are.
Beyond that it does nothing: isAuthorized is token !== null, nothing is parsed or validated. Refreshing, expiry and roles differ per backend, and a hook that guessed at them would be wrong for most — those go in the hook you write around this one.
Persistence is a decision, not a default
Nothing is written anywhere. No localStorage, no cookie, no interception — which keeps the choice between a cookie, a keychain and nothing at all in your hands, and means the hook renders the same on a server pass as it does in the browser, with nothing to guard and nothing to mismatch on hydration.
The way in is defaultValue, read once on mount: hand it whatever you had stored — reading it is yours to place, and a server pass has no localStorage to read. The way out is an override, since where a token is written is a side of the same decision.
// -constants/graft_session.ts
export const GraftSession = createGraft({ name: "GraftSession", graft: useGraft });
function useGraft() {
const auth = useGraftAuth<Session>({ defaultValue: readStoredSession() });
const onAuth = useCallback((session: Session) => {
writeStoredSession(session);
auth.onAuth(session);
}, [auth]);
return { ...auth, onAuth };
}