Skip to main content

Client Components

COSMOS RSC provides a set of client components for handling navigation transitions, slots, and errors. A component that wraps content to apply view transitions during navigation events.
import { NavigationTransition } from '../components/navigation-transition';

function Page() {
  return (
    <div>
      <NavigationTransition>
        <h1>My Page Title</h1>
      </NavigationTransition>
      <p>Other content...</p>
    </div>
  );
}

Props

Behavior

The component automatically applies view transitions based on navigation type:
  • navigation-back - Applied when navigating backwards
  • navigation-forward - Applied when navigating forwards
These transition types work with the browser’s View Transition API to create smooth animations between pages.

Example with Multiple Transitions

import { NavigationTransition } from '../components/navigation-transition';

export default function Page() {
  return (
    <div className="container">
      <NavigationTransition>
        <header>
          <h1>Welcome to My Site</h1>
        </header>
      </NavigationTransition>
      
      <main>
        <NavigationTransition>
          <section>
            <h2>Featured Content</h2>
            <p>This content will transition smoothly.</p>
          </section>
        </NavigationTransition>
      </main>
    </div>
  );
}

CSS Transitions

You can style the transitions using CSS:
::view-transition-old(root) {
  animation: fade-out 0.3s ease-out;
}

::view-transition-new(root) {
  animation: fade-in 0.3s ease-in;
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

Slot

A component that renders content passed through the slot context. Used for rendering nested layouts and pages.
'use client';

import { Slot } from '../../core/client/components/slot-context.js';

function Layout({ children }) {
  return (
    <div>
      <header>My App</header>
      <main>
        <Slot />
      </main>
      <footer>Footer</footer>
    </div>
  );
}

Usage

The Slot component reads from the SlotContext and renders the current page content. It’s automatically populated by the COSMOS RSC framework during navigation.
The Slot component must be used within a COSMOS RSC application context. It’s typically used in layout components to define where child content should render.

Example Layout

'use client';

import { Slot } from '../../core/client/components/slot-context.js';

export default function RootLayout() {
  return (
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
        </nav>
        <main>
          <Slot />
        </main>
      </body>
    </html>
  );
}

ErrorBoundary

A React error boundary component that catches JavaScript errors in child components and displays a fallback UI.
'use client';

import { ErrorBoundary } from '../../core/client/components/error-boundary.js';

function App() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      <MyComponent />
    </ErrorBoundary>
  );
}

Props

Default Fallback

When no fallback prop is provided, the error boundary displays:
<div>
  <h2>Something went wrong</h2>
  <p>{error.message}</p>
</div>

Example with Custom Fallback

'use client';

import { ErrorBoundary } from '../../core/client/components/error-boundary.js';

function CustomError({ error }) {
  return (
    <div className="error-container">
      <h1>Oops!</h1>
      <p>We encountered an unexpected error.</p>
      <details>
        <summary>Error details</summary>
        <pre>{error.message}</pre>
      </details>
      <button onClick={() => window.location.reload()}>
        Reload Page
      </button>
    </div>
  );
}

export default function Page() {
  return (
    <ErrorBoundary fallback={<CustomError />}>
      <DataFetchingComponent />
    </ErrorBoundary>
  );
}

Nested Error Boundaries

You can nest error boundaries to provide granular error handling:
'use client';

import { ErrorBoundary } from '../../core/client/components/error-boundary.js';

export default function Dashboard() {
  return (
    <div>
      <ErrorBoundary fallback={<div>Sidebar error</div>}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<div>Main content error</div>}>
        <MainContent />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<div>Widget error</div>}>
        <Widgets />
      </ErrorBoundary>
    </div>
  );
}

Error Recovery

The error boundary captures errors during:
  • Rendering
  • Lifecycle methods
  • Constructors of child components
Error boundaries do NOT catch errors in:
  • Event handlers (use try-catch)
  • Asynchronous code (use try-catch)
  • Server-side rendering errors
  • Errors thrown in the error boundary itself

Example with Streaming

'use client';

import { Suspense } from 'react';
import { ErrorBoundary } from '../../core/client/components/error-boundary.js';

function LoadingFallback() {
  return <div className="animate-pulse">Loading...</div>;
}

function ErrorFallback() {
  return <div className="error">Failed to load data</div>;
}

export default function StreamingPage() {
  return (
    <div>
      <h1>Streaming Data Demo</h1>
      
      <ErrorBoundary fallback={<ErrorFallback />}>
        <Suspense fallback={<LoadingFallback />}>
          <AsyncDataComponent />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

SlotContext

The underlying React context that provides slot content. Most applications should use the Slot component instead of accessing this context directly.
'use client';

import { use } from 'react';
import { SlotContext } from '../../core/client/components/slot-context.js';

function CustomSlot() {
  const content = use(SlotContext);
  return <div>{content}</div>;
}

SubmitButton

A client component that displays a loading state during form submission using React’s useFormStatus hook.
'use client';

import { SubmitButton } from '../components/submit-button';

export default function MyForm() {
  async function handleSubmit(formData) {
    'use server';
    // Process form data
  }
  
  return (
    <form action={handleSubmit}>
      <input name="email" type="email" />
      <SubmitButton>Submit</SubmitButton>
    </form>
  );
}

Props

Additional props are spread to the underlying <button> element.

Behavior

The component automatically:
  • Displays a loading spinner when the form is submitting
  • Disables the button during submission
  • Uses the useFormStatus hook to track form submission state

Implementation

The SubmitButton uses a grid layout to smoothly transition between the loading spinner and button content:
'use client';

import { useFormStatus } from 'react-dom';

export function SubmitButton({ children, ...props }) {
  const { pending } = useFormStatus();

  return (
    <button
      type='submit'
      className='relative inline-grid place-items-center rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none'
      disabled={pending}
      {...props}
    >
      <span
        aria-hidden='true'
        className={`col-start-1 row-start-1 transition-opacity ${
          pending ? 'opacity-100' : 'opacity-0'
        }`}
      >
        {/* Loading spinner */}
      </span>

      <span
        className={`col-start-1 row-start-1 transition-opacity ${
          pending ? 'opacity-0' : 'opacity-100'
        }`}
      >
        {children}
      </span>
    </button>
  );
}
The SubmitButton must be used inside a form component. The useFormStatus hook only works within a form context.

Example with Server Actions

'use client';

import { SubmitButton } from '../components/submit-button';

export default function ContactForm() {
  async function submitContact(formData) {
    'use server';
    const name = formData.get('name');
    const email = formData.get('email');
    const message = formData.get('message');
    
    // Process the form data
    console.log({ name, email, message });
  }
  
  return (
    <form action={submitContact} className="space-y-4">
      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          name="name"
          type="text"
          required
          className="w-full border rounded px-3 py-2"
        />
      </div>
      
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          name="email"
          type="email"
          required
          className="w-full border rounded px-3 py-2"
        />
      </div>
      
      <div>
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          name="message"
          required
          className="w-full border rounded px-3 py-2"
        />
      </div>
      
      <SubmitButton>Send Message</SubmitButton>
    </form>
  );
}

All client components are marked with 'use client' directive and are designed to work seamlessly with React Server Components.

Build docs developers (and LLMs) love