Skip to main content
Serial implements a local realtime database system to provide efficient, live-updating data across the application. This system leverages Redux for state management and Supabase for real-time data synchronization. Let’s dive into how this is implemented.

The Local Database Copy

The core of the local realtime database is a lightweight copy of the tenant’s database section, stored in the Redux store. This approach allows components throughout the app to access and interact with tenant data without making redundant database calls.

Redux Store Structure

The Redux store for the database is defined in redux/db.ts. It includes a slice with the following key elements:
  1. State Structure: The DbReduxState interface defines the structure of the database state, including properties for various entities like companies, components, processes, and users.
  2. Initial State: An initialState object sets up the initial empty state for all database entities.
  3. Reducers: The slice includes reducers for setting, inserting, updating, and deleting data in the state. For example:
    setCompany: (state, action) => {
      state.company = action.payload;
    },
    genericInsert: (state, action) => {
      const key = tableKeyMap[action.payload.table] as keyof DbReduxStateArraysOnly;
      (state[key] as any[]).push(action.payload.record);
    },
    
  4. Reset Action: A reduxDbReset action is provided to reset the state to its initial values.

Real-time Updates with useRealtime Hook

The useRealtime hook, defined in useRealtime.ts, is responsible for managing real-time updates from the Supabase database. Here’s how it works:
  1. State Management: The hook maintains state for whether it’s active and the timestamp of the last update.
  2. Subscription: The subscribe function sets up listeners for Supabase real-time events on various tables.
  3. Event Handling: When an event is received, the hook dispatches the appropriate Redux action to update the local state:
    const handleRealtimeEvent = (payload: SupabasePostgresRealtimePayload) => {
      switch (payload.eventType) {
        case "INSERT":
          dispatch(genericInsert({ table: payload.table, record: payload.new }));
          break;
        case "UPDATE":
          dispatch(genericUpdate({ table: payload.table, record: payload.new }));
          break;
        case "DELETE":
          dispatch(genericDelete({ table: payload.table, record: payload.old }));
          break;
      }
    };
    
  4. Refresh Logic: The hook includes logic to refresh critical data based on the current application path and to update foreign key relationships when relevant tables are modified.
  5. Cleanup: The hook ensures that the Supabase subscription is cleaned up when the component unmounts.

Putting It All Together

When the application initializes, it sets up the Redux store with the initial database state. The useRealtime hook is then used to subscribe to Supabase real-time events. As changes occur in the database, Supabase sends events to the client, which are processed by the useRealtime hook and dispatched to update the Redux store. This architecture allows Serial to maintain a local, always-up-to-date copy of the relevant database data, enabling responsive UI updates and reducing the need for frequent API calls. Components can simply connect to the Redux store to access the latest data, creating a seamless real-time experience for users. By combining the power of Redux for state management and Supabase for real-time database events, Serial achieves an efficient and scalable solution for live data updates across the application.