ScaleRocket/Mobile

Gestion d'état

Quand utiliser useState, useQuery, AsyncStorage, et pourquoi Redux n'est pas nécessaire dans ScaleRocket Mobile.

Gestion d'état

ScaleRocket Mobile utilise une approche simple de gestion d'état : les hooks intégrés de React pour l'état local et les requêtes en temps réel de votre backend pour l'état serveur. Pas besoin de Redux.

Catégories d'état

CatégorieOutilExemple
État UIuseStateChamps de formulaire, modal ouvert/fermé, chargement
État serveuruseQuery (backend)Données utilisateur, listes, métriques du tableau de bord
Préférences persistantesAsyncStorageThème, onboarding, langue
Données sécuriséesSecureStoreTokens d'authentification
État partagéReact ContextThème, session d'auth, toast

État local avec useState

Pour l'état qui appartient à un seul composant :

function ProfileForm() {
  const [name, setName] = useState("");
  const [loading, setLoading] = useState(false);
  const [showModal, setShowModal] = useState(false);

  return (
    <View>
      <Input label="Name" value={name} onChangeText={setName} />
      <Button title="Save" loading={loading} onPress={handleSave} />
    </View>
  );
}

État serveur

Utilisez les patterns React Query avec Supabase pour la récupération, la mise en cache et le re-fetching :

import { useEffect, useState } from "react";
import { supabase } from "@/lib/supabase";

function useProfile(userId: string) {
  const [data, setData] = useState<Profile | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    supabase
      .from("profiles")
      .select("*")
      .eq("id", userId)
      .single()
      .then(({ data }) => {
        setData(data);
        setIsLoading(false);
      });
  }, [userId]);

  return { data, isLoading };
}

Pour les mises à jour en temps réel, abonnez-vous aux changements :

useEffect(() => {
  const subscription = supabase
    .channel("profile")
    .on("postgres_changes", { event: "*", schema: "public", table: "profiles" }, (payload) => {
      setData(payload.new as Profile);
    })
    .subscribe();

  return () => { subscription.unsubscribe(); };
}, []);

Les requêtes Convex sont en temps réel par défaut. Les données se mettent à jour automatiquement quand elles changent sur le serveur :

import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

function Dashboard() {
  const profile = useQuery(api.users.getProfile);
  const metrics = useQuery(api.dashboard.getMetrics);

  if (profile === undefined || metrics === undefined) {
    return <Loading />;
  }

  return (
    <View>
      <Text>Welcome, {profile.name}</Text>
      <Text>Revenue: ${metrics.revenue}</Text>
    </View>
  );
}

Les mutations mettent à jour le serveur et toutes les requêtes se relancent automatiquement :

import { useMutation } from "convex/react";

function ProfileForm() {
  const updateProfile = useMutation(api.users.updateProfile);

  const handleSave = async () => {
    await updateProfile({ name: "New Name" });
    // Pas besoin de re-fetch — les requêtes se mettent à jour automatiquement
  };
}

État partagé avec Context

Pour l'état partagé entre plusieurs écrans, utilisez React Context :

// lib/auth-context.tsx
const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  // ... logique d'auth

  return (
    <AuthContext.Provider value={{ user, loading, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext)!;

AsyncStorage pour les préférences

Pour les données qui doivent persister entre les redémarrages de l'app mais qui ne sont pas sensibles :

import AsyncStorage from "@react-native-async-storage/async-storage";

// Hook personnalisé pour un état persistant
function usePersistentState<T>(key: string, defaultValue: T) {
  const [value, setValue] = useState<T>(defaultValue);

  useEffect(() => {
    AsyncStorage.getItem(key).then((stored) => {
      if (stored) setValue(JSON.parse(stored));
    });
  }, [key]);

  const setPersistentValue = (newValue: T) => {
    setValue(newValue);
    AsyncStorage.setItem(key, JSON.stringify(newValue));
  };

  return [value, setPersistentValue] as const;
}

Pourquoi pas Redux ?

ScaleRocket Mobile évite Redux car :

  • L'état serveur est géré par le système de requêtes de votre backend (temps réel Convex ou abonnements Supabase)
  • L'état UI est local aux composants avec useState
  • L'état partagé utilise React Context (auth, thème)
  • L'état persistant utilise AsyncStorage ou SecureStore

Cela couvre 100% des besoins typiques d'une app mobile sans le boilerplate de Redux, actions, reducers et middleware.

On this page