Global State

QuickDapp uses React Context for global state management. The application separates concerns into focused providers: theming, authentication, WebSocket connections, and toast notifications. Each handles one responsibility and exposes a hook for components to access its data.

Provider Structure

The App.tsx nests providers to make them available throughout the application:

<ThemeProvider>
  <QueryClientProvider client={queryClient}>
    <AuthProvider>
      <SocketProvider>
        <ToastProvider>
          {/* routes */}
        </ToastProvider>
      </SocketProvider>
    </AuthProvider>
  </QueryClientProvider>
</ThemeProvider>

Theme Context

The ThemeProvider manages dark/light mode with system preference detection.

interface ThemeContextValue {
  preference: ThemePreference    // "system" | "light" | "dark"
  resolvedTheme: ResolvedTheme   // "light" | "dark"
  setPreference: (preference: ThemePreference) => void
}

The provider resolves the "system" preference by checking window.matchMedia("(prefers-color-scheme: dark)") and listens for changes. The resolved theme is applied by adding "light" or "dark" to the HTML root element's class list, which activates the corresponding CSS variables.

Theme preference is persisted to localStorage under the "theme" key.

Access via useTheme(). See Theming for details on CSS integration.

Authentication Context

The AuthContext manages email and OAuth authentication. It uses a state machine to track the authentication lifecycle.

interface AuthContextValue {
  isAuthenticated: boolean
  isLoading: boolean
  error: Error | null
  authToken: string | null
  profile: UserProfile | null
  email: string | null
  login: (token: string, profile: UserProfile) => void
  logout: () => void
  restoreAuth: () => void
}

On mount, the context attempts to restore a previous session by reading the JWT from localStorage and validating it with the server. If valid, it fetches the user profile via the me query.

The context tracks several states: IDLE, RESTORING (checking for existing session), AUTHENTICATING, AUTHENTICATED, and ERROR.

Access the context via useAuthContext().

Socket Context

The SocketProvider manages WebSocket connections for real-time updates. It initializes after auth loading completes and automatically reconnects with the appropriate token when authentication state changes.

interface SocketContextValue {
  connected: boolean
  subscribe: (type: WebSocketMessageType, handler: (message: WebSocketMessage) => void) => () => void
}

When a user authenticates, the socket reconnects with their JWT to establish an authenticated session. When they log out, it reconnects without a token. The subscribe() method returns an unsubscribe function for cleanup.

Access the context via useSocket().

Toast Notifications

The ToastProvider manages temporary notification messages displayed in the UI. Toasts have types (default, success, error, warning), optional titles and descriptions, and auto-dismiss after a configurable duration.

interface ToastContextType {
  toasts: Toast[]
  addToast: (toast: Omit<Toast, "id">) => void
  removeToast: (id: string) => void
}

Toasts auto-dismiss after 5 seconds by default. The container renders in the top-right corner with slide-in animations.

Access the context via useToast().

Notification Hook

The useNotifications hook subscribes to real-time notification events from the WebSocket connection. It wraps the socket subscription for the NotificationReceived message type.

useNotifications({
  onNotificationReceived: (notification) => {
    // Handle notification
  }
})

This hook provides a cleaner API than directly subscribing to socket messages for notifications.

The CookieConsentProvider tracks whether users have accepted or declined cookies. It shows a banner component and persists the choice to localStorage.

interface CookieConsentContextValue {
  consent: "accepted" | "declined" | null
  hasConsented: boolean
  isAccepted: boolean
  isDeclined: boolean
  acceptCookies: () => void
  declineCookies: () => void
  resetConsent: () => void
}

This provider is not included in the default App.tsx but can be added when needed for GDPR compliance.

Access via useCookieConsent().