@msflib/react-profile¶
Standalone user-profile module for React applications. Built on TanStack Query v5. Supports real-time multi-tenant switching via
@msflib/core.
Table of Contents¶
- Installation
- Peer Dependencies
- Quick Start
- Configuration
- Application Config
- Custom Endpoints
- API Reference
- ProfileProvider
- useProfile
- Multi-Tenancy Support
- TypeScript Types
- Endpoint Defaults
Installation¶
pnpm add @msflib/react-profile @msflib/core @msflib/react-shared
Peer Dependencies¶
| Package | Version |
|---|---|
react |
>= 18 |
react-dom |
>= 18 |
@tanstack/react-query |
>= 5 |
Quick Start¶
1. Configure the application once (e.g. in your app's entry point):
// app/config.ts
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: process.env.NEXT_PUBLIC_API_URL!,
accessTokenKey: 'access_token',
endpoints: {
profile: {
profile: '/profiles', // default — override here if needed
},
},
});
2. Wrap your app with QueryClientProvider and ProfileProvider:
// app/providers.tsx
'use client'; // Next.js App Router
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ProfileProvider } from '@msflib/react-profile';
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<ProfileProvider>{children}</ProfileProvider>
</QueryClientProvider>
);
}
3. Use the useProfile hook in any child component:
import { useProfile } from '@msflib/react-profile';
export function ProfileCard() {
const { profile, loading, updateProfile } = useProfile();
if (loading.profile) return <p>Loading…</p>;
if (!profile) return <p>No profile found.</p>;
return (
<div>
<h2>
{profile.first_name} {profile.last_name}
</h2>
{profile.avatar && <img src={profile.avatar} alt="Avatar" />}
<button
disabled={loading.update}
onClick={() => updateProfile({ bio: 'Hello world!' })}
>
Update Bio
</button>
</div>
);
}
Configuration¶
Application Config¶
Call configureApplication once before rendering your app. It is idempotent — subsequent calls after the first are silently ignored.
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'access_token', // localStorage key for the JWT
workspace: 'acme', // optional: initial active workspace
endpoints: {
profile: {
/* override defaults here */
},
},
});
Custom Endpoints¶
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'my_token',
endpoints: {
profile: {
profile: '/v2/user/profiles',
},
},
});
API Reference¶
ProfileProvider¶
A React context provider that manages the current user's profile state. Must be rendered inside a QueryClientProvider.
import { ProfileProvider } from '@msflib/react-profile';
<QueryClientProvider client={queryClient}>
<ProfileProvider>{/* your app */}</ProfileProvider>
</QueryClientProvider>;
Props
| Prop | Type | Required | Description |
|---|---|---|---|
children |
React.ReactNode |
Yes | Child component tree. |
options |
object |
No | Configuration options for the provider. See below for details. |
options Configuration¶
| Key | Type | Default | Description |
|---|---|---|---|
isWorkspaceScoped |
boolean |
true |
If true, the profile is isolated per workspace. |
requireAuth |
boolean |
false |
If true, the profile will only be fetched when isAuthenticated is true. |
isAuthenticated |
boolean |
false |
The authentication status of the user (e.g. from useAuth().status). |
Behaviour
- On mount,
ProfileProviderautomatically fetchesGET /profilesto load the user's profile. - The result is cached for 60 seconds via TanStack Query (
staleTime: 60_000). - All mutations (
create,update,uploadAvatar,delete) automatically update the query cache on success — no manual invalidations needed.
useProfile¶
Returns the profile context. Must be called inside a component tree wrapped by ProfileProvider.
import { useProfile } from '@msflib/react-profile';
const {
profile,
createProfile,
updateProfile,
uploadAvatar,
deleteProfile,
getAccountProfile,
updateAccountProfile,
refetch,
loading,
} = useProfile();
profile¶
Profile | null — The current user's profile. Is null before the fetch resolves or when no profile exists.
createProfile(data, options?)¶
Creates a new profile for the current user (POST /profiles).
await createProfile({
first_name: 'Jane',
last_name: 'Smith',
date_of_birth: '1995-05-05',
gender: 'female',
marital_status: 'single',
});
Returns Promise<Profile>.
updateProfile(data, options?)¶
Partially updates the current user's profile (PUT /profiles).
await updateProfile({ first_name: 'Johnny', city: 'Austin' });
Returns Promise<Profile>.
uploadAvatar(data, options?)¶
Uploads or replaces the user's avatar image (PUT /profiles/avatar). Accepts a FormData object.
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const fd = new FormData();
fd.append('avatar', file);
uploadAvatar(fd);
};
Returns Promise<Profile>.
deleteProfile(options?)¶
Deletes the current user's profile (DELETE /profiles) and clears the cache.
await deleteProfile();
Returns Promise<Profile>.
getAccountProfile(accountId, options?)¶
Fetches another account's profile by id (GET /profiles/{account_id}) — e.g. for viewing a teammate's profile, not just your own. On success, the result is cached under its own account-scoped query key (it does not touch the current user's profile).
const teammate = await getAccountProfile(42);
Returns Promise<Profile>. For a declarative, cache-backed read instead of an imperative call, use the standalone useAccountProfile(accountId) hook (exported alongside useProfile):
import { useAccountProfile } from '@msflib/react-profile';
function TeammateCard({ accountId }: { accountId: number }) {
const { data: teammate, isPending } = useAccountProfile(accountId);
if (isPending) return <p>Loading…</p>;
return <h3>{teammate?.first_name}</h3>;
}
useAccountProfile is disabled (no request fired) while accountId is undefined.
updateAccountProfile(id, data, options?)¶
Updates another account's profile by id (PUT /profiles/{id}) — e.g. an admin editing a member's profile. Unlike updateProfile, this does not touch the current user's own profile cache.
await updateAccountProfile(42, { first_name: 'Johnny' });
Returns Promise<Profile>.
refetch()¶
Manually triggers a fresh fetch of the profile data.
refetch();
loading¶
An object of booleans indicating in-flight operations:
loading.profile; // true while the initial profile fetch is in progress
loading.create; // true while createProfile is in flight
loading.update; // true while updateProfile is in flight
loading.uploadAvatar; // true while uploadAvatar is in flight
loading.delete; // true while deleteProfile is in flight
loading.getAccountProfile; // true while getAccountProfile is in flight
loading.updateAccountProfile; // true while updateAccountProfile is in flight
Multi-Tenancy Support¶
@msflib/react-profile integrates with the workspace runtime in @msflib/core. All API requests are automatically prefixed with the active workspace, and the query cache reacts to workspace changes in real time.
How it works¶
- Endpoint paths — The API layer is built with
configuredApiClient()from@msflib/core, which applies the workspace prefix/header for scoped endpoints at request time. When no workspace is set, the path is used as-is. - Query cache isolation — The
profilequery key includes the current workspace:[workspace, 'msflib', 'profile']. Switching workspaces produces a new key, triggering a fresh network fetch automatically — no manual invalidation needed. - Real-time updates —
ProfileProviderusesuseActiveWorkspace()from@msflib/react-shared(backed byuseSyncExternalStore), so any call tosetActiveWorkspace()causes an immediate re-render and re-fetch with zero configuration.
Setting the initial workspace¶
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'access_token',
workspace: 'acme-corp', // seeds the active workspace on startup
});
Switching workspaces at runtime¶
import { setActiveWorkspace } from '@msflib/core';
// User switches workspace / organisation
setActiveWorkspace('new-workspace');
// ProfileProvider re-renders and re-fetches automatically, along with every
// other provider that subscribes via useActiveWorkspace() — notably,
// react-auth's AuthProvider does NOT, so it will not auto-refetch here.
Clearing the workspace¶
setActiveWorkspace(null); // or setActiveWorkspace('')
TypeScript Types¶
export type Profile = {
id: number;
first_name: string;
last_name: string;
date_of_birth: string;
gender: string;
marital_status: string;
phone?: string;
address?: string;
city?: string;
state?: string;
country?: string;
postal_code?: string;
avatar?: string;
bio?: string;
created_at: string;
updated_at: string;
[k: string]: unknown;
};
export type ProfilePayload = Omit<Profile, 'id' | 'created_at' | 'updated_at'>;
export type CreateProfilePayload = ProfilePayload;
export type UpdateProfilePayload = Partial<ProfilePayload>;
export type AvatarPayload = FormData;
Endpoint Defaults¶
| Key | Default | HTTP Method |
|---|---|---|
profile |
/profiles |
GET / POST / PUT / DELETE, plus GET/PUT on /{account_id} |
The same base path is used for all profile operations — the current user's own profile (GET/POST/PUT/DELETE /profiles), the avatar upload (PUT /profiles/avatar), and looking up/updating another account's profile by id (GET/PUT /profiles/{id}, via getAccountProfile/useAccountProfile/updateAccountProfile). Override via configureApplication({ endpoints: { profile: { profile: '/custom-path' } } }).