ScaleRocket/Mobile

State Management

When to use useState, useQuery, AsyncStorage, and why you don't need Redux in ScaleRocket Mobile.

State Management

ScaleRocket Mobile uses a simple state management approach: React's built-in hooks for local state and your backend's real-time queries for server state. No Redux needed.

State Categories

CategoryToolExample
UI stateuseStateForm inputs, modal open/close, loading
Server stateuseQuery (backend)User data, lists, dashboard metrics
Persistent preferencesAsyncStorageTheme, onboarding, language
Secure dataSecureStoreAuth tokens
Shared stateReact ContextTheme, auth session, toast

Local State with useState

For state that belongs to a single component:

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>
  );
}

Server State

Use React Query patterns with Supabase for data fetching, caching, and 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 };
}

For real-time updates, subscribe to changes:

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

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

Convex queries are real-time by default. Data updates automatically when it changes on the server:

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>
  );
}

Mutations update the server and all queries re-run automatically:

import { useMutation } from "convex/react";

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

  const handleSave = async () => {
    await updateProfile({ name: "New Name" });
    // No need to refetch — queries update automatically
  };
}

Shared State with Context

For state shared across multiple screens, use 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);

  // ... auth logic

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

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

AsyncStorage for Preferences

For data that should persist across app restarts but is not sensitive:

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

// Custom hook for persistent state
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;
}

Why Not Redux?

ScaleRocket Mobile avoids Redux because:

  • Server state is handled by your backend's query system (Convex real-time or Supabase subscriptions)
  • UI state is local to components with useState
  • Shared state uses React Context (auth, theme)
  • Persistent state uses AsyncStorage or SecureStore

This covers 100% of typical mobile app needs without the boilerplate of Redux, actions, reducers, and middleware.

On this page