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
| Category | Tool | Example |
|---|---|---|
| UI state | useState | Form inputs, modal open/close, loading |
| Server state | useQuery (backend) | User data, lists, dashboard metrics |
| Persistent preferences | AsyncStorage | Theme, onboarding, language |
| Secure data | SecureStore | Auth tokens |
| Shared state | React Context | Theme, 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.