Skip to content

Instantly share code, notes, and snippets.

@JLarky
Last active April 5, 2025 13:02
Show Gist options
  • Save JLarky/190bab52ff13c44f9420523d1792fbf0 to your computer and use it in GitHub Desktop.
Save JLarky/190bab52ff13c44f9420523d1792fbf0 to your computer and use it in GitHub Desktop.
useChildFormStatus

Why is useFormStatus always returning pending false?

So you are trying out that new fangled useFormStatus hook but it doesn't actually react to the status of your form?

Now you tried to Google it and all you get is RTFM "go read docs again". Because hook useFormStatus shows you the status of parent form, not a sibling one.

Let's explain that in English.

  • <form><Spinner></form> works
  • <Spinner><form></form> doesn't

Thus useChildFormStatus was born, it allows you to lift the form state up to the parent component.

export const Test = () => {
const { status, Listener } = useChildFormStatus();
const { pending } = status;
return (
<form
action={async (formData) => {
await new Promise((resolve) => setTimeout(resolve, 300));
for (const [key, value] of formData) {
console.log(key, value);
}
}}
>
<input type="text" name="name" />
<input name="submit" type="submit" value="Submit" disabled={pending} />
<input name="submit2" type="submit" value="Submit2" disabled={pending} />
<Listener />
</form>
);
};
const useChildFormStatus = () => {
const [status, setStatus] = React.useState<ReturnType<typeof useFormStatus>>({
pending: false,
data: null,
method: null,
action: null,
});
const Listener = React.useCallback(() => {
const currentStatus = useFormStatus();
React.useEffect(() => {
setStatus(currentStatus);
}, [currentStatus]);
return null;
}, [setStatus]);
return { status, Listener };
};
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React from 'react';
import ReactDom from 'react-dom';
const useFormStatus = (
ReactDom as any as {
experimental_useFormStatus: () => {
pending: boolean;
data: FormData | null;
method: 'get' | 'post' | null;
action: ((formData: FormData) => Promise<void>) | null;
};
}
).experimental_useFormStatus;
export const Test = () => {
const { status, Listener } = useChildFormStatus();
const { pending } = status;
return (
<form
// @ts-ignore
action={async (formData: FormData) => {
await new Promise((resolve) => setTimeout(resolve, 300));
for (const [key, value] of formData) {
console.log(key, value);
}
}}
>
<input type="text" name="name" />
<input name="submit" type="submit" value="Submit" disabled={pending} />
<input name="submit2" type="submit" value="Submit2" disabled={pending} />
<Listener />
</form>
);
};
const useChildFormStatus = () => {
const [status, setStatus] = React.useState<ReturnType<typeof useFormStatus>>({
pending: false,
data: null,
method: null,
action: null,
});
const Listener = React.useCallback(() => {
const currentStatus = useFormStatus();
React.useEffect(() => {
setStatus(currentStatus);
}, [currentStatus]);
return null;
}, [setStatus]);
return { status, Listener };
};
export const Test = () => {
return (
<form
action={async (formData) => {
await new Promise((resolve) => setTimeout(resolve, 300));
for (const [key, value] of formData) {
console.log(key, value);
}
}}
>
<input type="text" name="name" />
<Submit />
<Submit2 />
</form>
);
};
const Submit = () => {
const { pending } = useFormStatus();
return <input name="submit" type="submit" value="Submit" disabled={pending} />;
};
const Submit2 = () => {
const { pending } = useFormStatus();
return <input name="submit2" type="submit" value="Submit2" disabled={pending} />;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment