Skip to content

Commit 58fba8e

Browse files
committed
feat: simplify form submit logic via event.submitter
Copies the changes from remix remix-run/remix#3094
1 parent c3718ff commit 58fba8e

File tree

2 files changed

+20
-75
lines changed

2 files changed

+20
-75
lines changed

packages/react-router-dom/__tests__/DataBrowserRouter-test.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,9 @@ function testDomRouter(name, TestDataRouter, getWindow) {
967967
</TestDataRouter>
968968
);
969969

970+
// Note: jsdom doesn't properly attach event.submitter for
971+
// <button type="submit"> clicks, so we have to use an input to drive
972+
// this. See https://github.com/jsdom/jsdom/issues/3117
970973
function Comp() {
971974
let fetcher = useFetcher();
972975
return (
@@ -977,14 +980,12 @@ function testDomRouter(name, TestDataRouter, getWindow) {
977980
{fetcher.data ? JSON.stringify(fetcher.data) : null}
978981
</p>
979982
<fetcher.Form>
980-
<button type="submit" name="increment" value="1">
981-
submit get 1
982-
</button>
983+
<input name="increment" value="1" />
984+
<button type="submit">submit get 1</button>
983985
</fetcher.Form>
984986
<fetcher.Form method="post">
985-
<button type="submit" name="increment" value="10">
986-
submit post 10
987-
</button>
987+
<input name="increment" value="10" />
988+
<button type="submit">submit post 10</button>
988989
</fetcher.Form>
989990
</>
990991
);

packages/react-router-dom/index.tsx

Lines changed: 13 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,14 @@ if (__DEV__) {
518518
Form.displayName = "Form";
519519
}
520520

521+
type HTMLSubmitEvent = React.BaseSyntheticEvent<
522+
SubmitEvent,
523+
Event,
524+
HTMLFormElement
525+
>;
526+
527+
type HTMLFormSubmitter = HTMLButtonElement | HTMLInputElement;
528+
521529
interface FormImplProps extends FormProps {
522530
fetcherKey?: string;
523531
}
@@ -539,66 +547,20 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
539547
let formMethod: FormMethod =
540548
method.toLowerCase() === "get" ? "get" : "post";
541549
let formAction = useFormAction(action);
542-
let formRef = React.useRef<HTMLFormElement>();
543-
let ref = useComposedRefs(forwardedRef, formRef);
544-
545-
// When calling `submit` on the form element itself, we don't get data from
546-
// the button that submitted the event. For example:
547-
//
548-
// <Form>
549-
// <button name="something" value="whatever">Submit</button>
550-
// </Form>
551-
//
552-
// formData.get("something") should be "whatever", but we don't get that
553-
// unless we call submit on the clicked button itself.
554-
//
555-
// To figure out which button triggered the submit, we'll attach a click
556-
// event listener to the form. The click event is always triggered before
557-
// the submit event (even when submitting via keyboard when focused on
558-
// another form field, yeeeeet) so we should have access to that button's
559-
// data for use in the submit handler.
560-
let clickedButtonRef = React.useRef<any>();
561-
562-
React.useEffect(() => {
563-
let form = formRef.current;
564-
if (!form) return;
565-
566-
function handleClick(event: MouseEvent) {
567-
if (!(event.target instanceof Element)) return;
568-
let submitButton = event.target.closest<
569-
HTMLButtonElement | HTMLInputElement
570-
>("button,input[type=submit]");
571-
572-
if (
573-
submitButton &&
574-
submitButton.form === form &&
575-
submitButton.type === "submit"
576-
) {
577-
clickedButtonRef.current = submitButton;
578-
}
579-
}
580-
581-
window.addEventListener("click", handleClick);
582-
return () => {
583-
window.removeEventListener("click", handleClick);
584-
};
585-
}, []);
586-
587550
let submitHandler: React.FormEventHandler<HTMLFormElement> = (event) => {
588551
onSubmit && onSubmit(event);
589552
if (event.defaultPrevented) return;
590553
event.preventDefault();
591554

592-
submit(clickedButtonRef.current || event.currentTarget, {
593-
method,
594-
replace,
595-
});
596-
clickedButtonRef.current = null;
555+
let submitter = (event as unknown as HTMLSubmitEvent).nativeEvent
556+
.submitter as HTMLFormSubmitter | null;
557+
558+
submit(submitter || event.currentTarget, { method, replace });
597559
};
598560

599561
return (
600562
<form
601-
ref={ref}
563+
ref={forwardedRef}
602564
method={formMethod}
603565
action={formAction}
604566
encType={encType}
@@ -823,24 +785,6 @@ export function useFormAction(action = "."): string {
823785
return pathname + search;
824786
}
825787

826-
function useComposedRefs<RefValueType = any>(
827-
...refs: Array<React.Ref<RefValueType> | null | undefined>
828-
): React.RefCallback<RefValueType> {
829-
return React.useCallback((node) => {
830-
for (let ref of refs) {
831-
if (ref == null) continue;
832-
if (typeof ref === "function") {
833-
ref(node);
834-
} else {
835-
try {
836-
(ref as React.MutableRefObject<RefValueType>).current = node!;
837-
} catch (_) {}
838-
}
839-
}
840-
// eslint-disable-next-line react-hooks/exhaustive-deps
841-
}, refs);
842-
}
843-
844788
function createFetcherForm(fetcherKey: string) {
845789
let FetcherForm = React.forwardRef<HTMLFormElement, FormProps>(
846790
(props, ref) => {

0 commit comments

Comments
 (0)