Back to Blog
SupabaseValtioReal-TimeState ManagementFinancial Apps

Integrating Supabase and Valtio for Real-Time State Management in Financial Apps

A deep dive into combining Supabase's real-time subscriptions with Valtio's proxy-based state management to build responsive, data-driven financial applications.

P

Prizmstack Team

January 15, 2026

3 min read
553 words
Integrating Supabase and Valtio for Real-Time State Management in Financial Apps

Building financial applications demands instant data updates, robust state synchronization, and a developer experience that scales with complexity. In this post, we walk through how we combine Supabase for real-time database subscriptions with Valtio for lightweight, proxy-based state management.


Why This Stack?

| Concern | Supabase | Valtio | |---------|----------|--------| | Real-time data | Postgres + Realtime channels | — | | Client state | — | Proxy-based reactivity | | Auth | Built-in Row Level Security | — | | Bundle size | Server-side | ~3 kB |

Financial dashboards often display live portfolio values, transaction feeds, and alerts. Supabase's Realtime feature pushes row-level changes over WebSockets, while Valtio lets React components subscribe to only the slices of state they need—no context providers or reducers required.


Architecture Overview

┌──────────────┐      WebSocket       ┌────────────────┐
│   Supabase   │ ──────────────────▶  │  Valtio Store  │
│   Realtime   │                      │  (proxy state) │
└──────────────┘                      └───────┬────────┘
                                              │
                                              ▼
                                      ┌───────────────┐
                                      │ React UI      │
                                      │ (useSnapshot) │
                                      └───────────────┘
  1. Supabase client subscribes to a Postgres channel (e.g., transactions).
  2. On INSERT, UPDATE, or DELETE, the callback mutates the Valtio store.
  3. React components using useSnapshot re-render automatically.

Step-by-Step Implementation

1. Define the Valtio Store

// lib/stores/transactionStore.ts
import { proxy } from 'valtio';
import type { Transaction } from '@/lib/types';

/**
 * Proxy store for transactions.
 * Mutations here trigger React re-renders via useSnapshot.
 */
export const transactionStore = proxy<{
  transactions: Transaction[];
  isLoading: boolean;
}>({
  transactions: [],
  isLoading: true,
});

2. Subscribe to Supabase Realtime

// lib/services/subscribeTransactions.ts
import { supabase } from '@/lib/supabase/client';
import { transactionStore } from '@/lib/stores/transactionStore';

export function subscribeTransactions(userId: string) {
  // Initial fetch
  supabase
    .from('transactions')
    .select('*')
    .eq('user_id', userId)
    .order('created_at', { ascending: false })
    .then(({ data }) => {
      transactionStore.transactions = data ?? [];
      transactionStore.isLoading = false;
    });

  // Realtime subscription
  const channel = supabase
    .channel('transactions-realtime')
    .on(
      'postgres_changes',
      { event: '*', schema: 'public', table: 'transactions', filter: `user_id=eq.${userId}` },
      (payload) => {
        switch (payload.eventType) {
          case 'INSERT':
            transactionStore.transactions.unshift(payload.new as Transaction);
            break;
          case 'UPDATE':
            const idx = transactionStore.transactions.findIndex((t) => t.id === payload.new.id);
            if (idx !== -1) transactionStore.transactions[idx] = payload.new as Transaction;
            break;
          case 'DELETE':
            transactionStore.transactions = transactionStore.transactions.filter(
              (t) => t.id !== payload.old.id
            );
            break;
        }
      }
    )
    .subscribe();

  // Return unsubscribe function for cleanup
  return () => {
    supabase.removeChannel(channel);
  };
}

3. Consume in React

// components/TransactionFeed.tsx
'use client';

import { useSnapshot } from 'valtio';
import { transactionStore } from '@/lib/stores/transactionStore';

export function TransactionFeed() {
  const { transactions, isLoading } = useSnapshot(transactionStore);

  if (isLoading) return <p>Loading...</p>;

  return (
    <ul>
      {transactions.map((tx) => (
        <li key={tx.id}>
          {tx.description} — ${tx.amount}
        </li>
      ))}
    </ul>
  );
}

Key Takeaways

  • Decoupled data layer: Supabase handles persistence and real-time; Valtio handles UI state.
  • Minimal boilerplate: No Redux slices, no context wrappers.
  • Scalable pattern: Add more tables/channels without restructuring your state tree.

When to Use This Pattern

  • Dashboards with live metrics (finance, analytics, IoT).
  • Collaborative apps where multiple users edit the same data.
  • Any scenario where you need sub-second UI updates from a Postgres backend.

Further Reading


Have questions or want to see this pattern in action? Contact us to schedule a technical deep-dive.

Topics covered

SupabaseValtioReal-TimeState ManagementFinancial Apps
P

Written by Prizmstack Team

Full-spectrum software agency

Ready to build something remarkable?

Let's discuss how we can help bring your vision to life with the same technical expertise shared in this article.

Integrating Supabase and Valtio for Real-Time State Management in Financial Apps | Prizmstack