État global
Certaines informations servent dans toute l'app et doivent survivre aux changements d'écran : qui est connecté, la liste des équipes... Elles vivent dans src/state/, sous forme de contextes React (voir Les bases).
Chaque fichier suit le même modèle :
const XContext = createContext<XValue | null>(null);
export function XProvider({ children }) {
// l'état et les fonctions
return <XContext.Provider value={value}>{children}</XContext.Provider>;
}
export function useX() {
const value = useContext(XContext);
if (!value) throw new Error('useX must be used inside XProvider');
return value;
}Le throw protège contre un oubli : si un composant utilise useX() en dehors du provider, l'erreur est claire au lieu d'un plantage mystérieux plus loin.
La session : src/state/session.tsx
type SessionContextValue = {
ready: boolean; // a-t-on fini de vérifier la session au démarrage ?
user: SignedInUser | null; // l'utilisateur connecté, ou null
signIn: (email, password) => Promise<void>;
signOut: () => Promise<void>; // POST /auth/logout puis forget()
forget: () => Promise<void>; // oublie la session localement, sans appeler l'API
refresh: () => Promise<void>; // recharge l'utilisateur (après un changement de nom, de langue...)
};| Hook | Renvoie | Quand l'utiliser |
|---|---|---|
useSession() | Tout l'objet ci-dessus | Quand l'utilisateur peut être null (écrans publics, layout) |
useCurrentUser() | L'utilisateur, jamais null | Dans les onglets, où l'on est forcément connecté |
useCurrentUser() lève une erreur s'il n'y a personne de connecté. Dans (tabs)/, c'est impossible grâce au garde de _layout.tsx, donc on peut écrire user.id sans vérifier.
La langue suit le compte
Chaque fois que l'utilisateur change (connexion, déconnexion, refresh), la session applique sa langue :
function applyLocale(user: SignedInUser | null) {
i18n.changeLanguage(user?.preferences.locale ?? deviceLocale());
}Si le compte a choisi le français sur le web, l'app passe en français. Sans choix, on prend la langue du téléphone.
L'annuaire : src/state/directory.tsx
Les managers et le GM ont besoin de la liste des personnes et des équipes dans beaucoup d'écrans (Équipe, fiche d'un membre, demandes, utilisateurs...). Plutôt que de la recharger partout, elle est chargée une fois ici.
type DirectoryContextValue = {
users: User[];
teams: Team[];
findUser: (id: number) => User | undefined;
reload: () => Promise<void>;
};const reload = useCallback(async () => {
if (!user || user.role === 'employee') {
setUsers(user ? [user] : []);
setTeams([]);
return;
}
const [allUsers, allTeams] = await Promise.all([fetchUsers(), fetchTeams()]);
setUsers(allUsers);
setTeams(allTeams);
}, [user]);| Rôle | users | teams |
|---|---|---|
| Employé | Lui-même seulement | Vide (l'API refuserait de toute façon) |
| Manager | Ce que l'API lui renvoie | Ses équipes |
| GM | Tout le monde | Toutes les équipes |
Promise.all lance les deux requêtes en même temps au lieu de l'une après l'autre.
findUser(id) cherche d'abord dans users, puis parmi les managers et membres des équipes. Un manager peut ainsi retrouver le nom d'un membre même si /users ne le renvoie pas.
Quand l'annuaire est rechargé
- À chaque changement d'utilisateur (connexion).
- Après chaque action réussie, par
useAction(créer une équipe, changer un rôle...). - Quand certains écrans s'affichent, avec
useRefreshDirectory()(Équipe, Utilisateurs, Plus, détail d'équipe).
La présentation : src/state/onboarding.tsx
type OnboardingContextValue = {
ready: boolean; // a-t-on fini de lire SecureStore ?
seen: boolean; // la présentation a-t-elle été vue ?
finish: () => void;
};Au démarrage, il lit la clé timemanager.onboardingSeen dans SecureStore. finish() la passe à yes. Le layout racine utilise seen pour choisir entre la présentation et la connexion.
La présentation peut être revue depuis Compte, « Revoir la présentation ». Dans ce cas, finish() ramène simplement à l'écran précédent.
Les messages : src/components/ui/toast.tsx
Ce n'est pas dans state/, mais c'est aussi un contexte global. ToastProvider affiche un bandeau en haut de l'écran pendant 4 secondes.
const showToast = useToast();
showToast(t('settings.saved'));Le bandeau est annoncé par VoiceOver et TalkBack (accessibilityRole="alert"). Le plus souvent, on ne l'appelle pas directement : useAction le fait (voir Lire et modifier des données).
Ce qui n'est pas global
Tout le reste est local à l'écran qui en a besoin : les heures, les corrections, les notifications... Ces données sont rechargées à chaque affichage de l'écran par useQuery. C'est plus simple et toujours à jour.