GioJSdocs
On this page

useGioFormState

Read the state of the enclosing <GioForm> - whether a submission is pending and how the last one ended - from any component inside it.

app/components/submit-button.tsx
import type { ReactNode } from 'react';
import { useGioFormState } from '@gio.js/react';

export function SubmitButton({ children }: { children: ReactNode }) {
  const { pending } = useGioFormState();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Saving...' : children}
    </button>
  );
}

Reference

Parameters

useGioFormState takes no parameters.

Returns

A GioFormState:

FieldTypeDefaultDescription
pendingboolean-true from the moment the form submits until the answer is on screen.
lastResultGioFormResult | null-How the last finished submission ended - ok, status, url, redirected, data, response, error (see GioFormResult). null before the first.

Behavior

  • It reads the nearest <GioForm> above the component. Outside one it always returns { pending: false, lastResult: null }.
  • pending stays true while the browser loads a page the router cannot render (a redirect to another site), so a button stays disabled until the page goes away; it ends if the back/forward cache brings the page back, and for a file download.
  • A submission overtaken by a newer navigation ends pending and keeps the previous lastResult.
  • Before hydration, and without JavaScript, the form posts natively and the state stays idle.

Examples

A status line

app/settings/save-status.tsx
import { useGioFormState } from '@gio.js/react';

export function SaveStatus() {
  const { pending, lastResult } = useGioFormState();
  if (pending) return <p aria-live="polite">Saving...</p>;
  if (lastResult === null) return null;
  if (lastResult.status === 0) return <p role="alert">Network error - your changes are still here.</p>;
  if (!lastResult.ok) return <p role="alert">Could not save (HTTP {lastResult.status}).</p>;
  return <p aria-live="polite">Saved.</p>;
}
app/settings/page.tsx
import type { ActionArgs } from '@gio.js/core';
import { GioForm } from '@gio.js/react';
import { SaveStatus } from './save-status.tsx';
import { SubmitButton } from '../components/submit-button.tsx';

export async function action(req: ActionArgs) {
  const form = await req.formData();
  await settings.save({ theme: String(form.get('theme')) });
  return { saved: true };
}

export default function Settings() {
  return (
    <GioForm>
      <select name="theme">
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <SubmitButton>Save</SubmitButton>
      <SaveStatus />
    </GioForm>
  );
}

Without a separate component

The hook needs a component inside the form. For a one-off, pass a function as the form's children; it receives the same state.

tsx
<GioForm>
  {({ pending }) => <button disabled={pending}>{pending ? 'Sending...' : 'Send'}</button>}
</GioForm>

Good to know

  • Called in the component that renders the <GioForm>, it reads the form around that component, not this one - which is usually none, so it is idle.
  • The form itself also exposes pending as aria-busy="true", for CSS: form[aria-busy="true"] { opacity: 0.6 }.
  • While pending is true, further submits of that form are dropped, whether or not you disable the button.

Version history

VersionChanges
v0.1.0-beta.8Introduced.