The tea shop's admin pages have many forms, and every one needs a button that says it's busy while the form saves. Build SubmitButton (a named export) so no form has to pass it a pending prop.
SubmitButton takes children, the button's label, which defaults to Save. It's a submit button. While the action of the form it sits in is running, it shows Saving… (with the single … character) instead of its label and is disabled. Before and after, it shows its label and is enabled.
Use useFormStatus from react-dom: it reports on the form the component is rendered inside, so two forms on the same page each have their own status.
The default export App is already written: a form that saves a tasting note with its save(note) prop and then shows Saved: followed by the note. It uses your button with the label Save note.
<form action={saveNote}>
<textarea name="note" />
<SubmitButton>Save note</SubmitButton>
</form>
// Idle: [Save note]
// Saving: [Saving…] (disabled)
render(
<form action={() => {}}>
<SubmitButton>Order</SubmitButton>
</form>,
);
expect(screen.getByRole("button", { name: "Order" })).toBeEnabled();
render(
<form action={() => {}}>
<SubmitButton />
</form>,
);
expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument();
const action = spy();
render(
<form action={action}>
<input name="tea" defaultValue="Sencha" aria-label="Tea" />
<SubmitButton>Order</SubmitButton>
</form>,
);
await user.click(screen.getByRole("button", { name: "Order" }));
expect(action).toHaveBeenCalledTimes(1);
expect(action.calls[0].args[0].get("tea")).toBe("Sencha");
const answer = deferred();
render(
<form action={() => answer.promise}>
<SubmitButton>Order</SubmitButton>
</form>,
);
await user.click(screen.getByRole("button", { name: "Order" }));
expect(screen.getByRole("button", { name: "Saving…" })).toBeDisabled();
answer.resolve();
await flush();
const answer = deferred();
render(
<form action={() => answer.promise}>
<SubmitButton>Order</SubmitButton>
</form>,
);
await user.click(screen.getByRole("button", { name: "Order" }));
answer.resolve();
expect(await screen.findByRole("button", { name: "Order" })).toBeEnabled();
expect(screen.queryByText("Saving…")).not.toBeInTheDocument();
const answer = deferred();
render(
<>
<form action={() => answer.promise}>
<SubmitButton>Order</SubmitButton>
</form>
<form action={() => {}}>
<SubmitButton>Review</SubmitButton>
</form>
</>,
);
await user.click(screen.getByRole("button", { name: "Order" }));
expect(screen.getByRole("button", { name: "Saving…" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Review" })).toBeEnabled();
answer.resolve();
await flush();
const answer = deferred();
const save = spy(() => answer.promise);
render(<App save={save} />);
await user.type(screen.getByLabelText("Tasting note"), "Grassy and sweet");
await user.click(screen.getByRole("button", { name: "Save note" }));
expect(save).toHaveBeenCalledWith("Grassy and sweet");
expect(screen.getByRole("button", { name: "Saving…" })).toBeDisabled();
answer.resolve();
expect(await screen.findByText("Saved: Grassy and sweet")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save note" })).toBeEnabled();
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Submit button with useFormStatus. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.