Skip to content

Return Values

hiraku supports type-safe return values from modals using the .returns<T>() method.

Defining Return Types

tsx
interface FormData {
  name: string;
  email: string;
}

function UserFormDialog() {
  const [form, setForm] = useState<FormData>({ name: "", email: "" });

  return (
    <DialogContent>
      <input
        value={form.name}
        onChange={(e) => setForm({ ...form, name: e.target.value })}
      />
      <input
        value={form.email}
        onChange={(e) => setForm({ ...form, email: e.target.value })}
      />
      <Button onClick={() => userFormDialog.close({ data: form, role: "confirm" })}>
        Submit
      </Button>
    </DialogContent>
  );
}

// Specify the return type
export const userFormDialog = createDialog(UserFormDialog).returns<FormData>();

Receiving Return Values

Use onDidClose() to receive the result:

tsx
async function handleCreateUser() {
  await userFormDialog.open();
  
  const result = await userFormDialog.onDidClose();
  // result: { data?: FormData, role?: ModalRole }

  if (result.role === "confirm" && result.data) {
    await createUser(result.data);
  }
}

Result Structure

The onDidClose() promise resolves with:

tsx
interface ModalResult<T> {
  data?: T;          // The return value (if any)
  role?: ModalRole;  // How the modal was closed
}

type ModalRole = "confirm" | "cancel" | "dismiss" | (string & {});

Examples

Boolean Confirmation

tsx
export const confirmDialog = createDialog(ConfirmDialog).returns<boolean>();

// Usage
const { data, role } = await confirmDialog.onDidClose();
if (role === "confirm" && data === true) {
  // Confirmed
}

Form Data

tsx
interface ContactForm {
  name: string;
  email: string;
  message: string;
}

export const contactDialog = createDialog(ContactDialog).returns<ContactForm>();

// Usage
const { data } = await contactDialog.onDidClose();
if (data) {
  await sendMessage(data);
}

Selection

tsx
export const colorPicker = createDialog(ColorPickerDialog).returns<string>();

// Usage
const { data: selectedColor } = await colorPicker.onDidClose();
if (selectedColor) {
  applyColor(selectedColor);
}

Multiple Values

tsx
interface FilterResult {
  categories: string[];
  priceRange: [number, number];
  sortBy: "price" | "date" | "name";
}

export const filterSheet = createSheet(FilterSheet).returns<FilterResult>();

No Return Value

If you don't need a return value, simply omit .returns():

tsx
export const infoDialog = createDialog(InfoDialog);

// Still get the role
const { role } = await infoDialog.onDidClose();
if (role === "dismiss") {
  // User dismissed the dialog
}

Released under the MIT License.