Client + Server Action

Client Action Calls Server Action

When you have both a client action and a server action, the client action can perform validation, generate optimistic IDs, then call serverAction() to persist data. This pattern provides fast client feedback while keeping mutations secure on the server.

Why Use This Pattern?

  • Instant Validation: Validate on the client before hitting the server
  • Optimistic Updates: Generate client-side IDs for instant UI updates
  • Security: All actual mutations happen server-side
  • Enrichment: Track client-side metrics like timing

Create Record

Combined Result

Submit the form to see client + server action in action

Client Action Calling Server (client-calls-server.tsx)

import type { RouteActionArgs } from "@udibo/juniper";

// ServerMutationResult type is defined in the same .tsx file
export interface ServerMutationResult {
  success: boolean;
  message: string;
  savedAt: string;
  recordId: string;
}

interface ClientEnrichedResult extends ServerMutationResult {
  clientValidated: boolean;
  optimisticId: string;
  totalProcessingTime: number;
}

export async function action({
  request,
  serverAction,
}: RouteActionArgs): Promise<ClientEnrichedResult> {
  const startTime = Date.now();
  const formData = await request.formData();
  const title = formData.get("title") as string;

  if (title.length < 3) {
    return { success: false, message: "Title too short", ... };
  }

  const optimisticId = `opt-${Date.now()}`;
  const serverResult = await serverAction() as ServerMutationResult;

  return {
    ...serverResult,
    clientValidated: true,
    optimisticId,
    totalProcessingTime: Date.now() - startTime,
  };
}

Note: Try submitting with short values to see client validation fail instantly, without making a server request. Valid submissions go through to the server.