Skip to content

Commit

Permalink
update Form to use callbacks and add focusFieldAt to ref
Browse files Browse the repository at this point in the history
  • Loading branch information
JDMathew committed Jul 31, 2024
1 parent 018549a commit 7eb9f09
Showing 1 changed file with 72 additions and 59 deletions.
131 changes: 72 additions & 59 deletions packages/forms/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { InteractionManager } from 'react-native';

export type FormProps = React.PropsWithChildren<{
onSubmit: () => boolean | Promise<boolean>;
ref?: React.RefObject<FormActions>;
}>;

export type FormActions = {
focusFirstInvalidField: () => void;
focusFieldAt: (fieldNumber: number) => void;
};

export const Form = React.forwardRef<FormActions, FormProps>(
Expand All @@ -19,82 +19,95 @@ export const Form = React.forwardRef<FormActions, FormProps>(

const checks = __DEV__ ? useChecks?.() : undefined;

const focusField = (nextField?: Partial<FormRef> | undefined) => {
/**
* Refs passed as prop have another ".current"
*/
const nextRefElement = nextField?.ref?.current?.current
? nextField?.ref.current
: nextField?.ref;
const focusField = React.useCallback(
(nextField?: Partial<FormRef> | undefined) => {
/**
* Refs passed as prop have another ".current"
*/
const nextRefElement = nextField?.ref?.current?.current
? nextField?.ref.current
: nextField?.ref;

const callFocus =
// @ts-ignore
nextRefElement?.current?.focus &&
nextField?.hasFocusCallback &&
nextField?.isEditable;

__DEV__ &&
nextRefElement == null &&
checks?.logResult('nextRefElement', {
message:
'No next field found. Make sure you wrapped your form inside the <Form /> component',
rule: 'NO_UNDEFINED',
});

if (callFocus) {
/**
* On some apps, if we call focus immediately and the field is already focused we lose the focus.
* Same happens if we do not call `focus` if the field is already focused.
*/
const timeoutValue = isFocused(nextRefElement.current) ? 50 : 0;

setTimeout(() => {
nextRefElement?.current?.focus();

__DEV__ &&
nextRefElement &&
checks?.checkFocusTrap({
ref: nextRefElement,
shouldHaveFocus: true,
});
}, timeoutValue);
} else if (nextRefElement?.current) {
setFocus(nextRefElement?.current);
}
},
[checks, setFocus],
);

const callFocus =
// @ts-ignore
nextRefElement?.current?.focus &&
nextField?.hasFocusCallback &&
nextField?.isEditable;
const focusFieldAt = React.useCallback(
(position: number) => {
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
const fieldWithError = refs.current[position];

focusField(fieldWithError);
}, 0);
});
},
[focusField],
);

const focusFirstInvalidField = React.useCallback(() => {
const fieldWithError = (refs.current || []).findIndex(
fieldRef => fieldRef.hasValidation && fieldRef.hasError,
);

__DEV__ &&
nextRefElement == null &&
checks?.logResult('nextRefElement', {
fieldWithError == null &&
checks?.logResult('Form', {
message:
'No next field found. Make sure you wrapped your form inside the <Form /> component',
'The form validation has failed, but no component with error was found',
rule: 'NO_UNDEFINED',
});

if (callFocus) {
/**
* On some apps, if we call focus immediately and the field is already focused we lose the focus.
* Same happens if we do not call `focus` if the field is already focused.
*/
const timeoutValue = isFocused(nextRefElement.current) ? 50 : 0;

setTimeout(() => {
nextRefElement?.current?.focus();

__DEV__ &&
nextRefElement &&
checks?.checkFocusTrap({
ref: nextRefElement,
shouldHaveFocus: true,
});
}, timeoutValue);
} else if (nextRefElement?.current) {
setFocus(nextRefElement?.current);
}
};
focusFieldAt(fieldWithError);
}, [focusFieldAt, checks]);

const submitForm = async () => {
const submitForm = React.useCallback(async () => {
const isValid = await onSubmit();

if (isValid) {
return;
}

focusFirstInvalidField();
};

const focusFirstInvalidField = () => {
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
const fieldWithError = (refs.current || []).find(
fieldRef => fieldRef.hasValidation && fieldRef.hasError,
);

__DEV__ &&
fieldWithError == null &&
checks?.logResult('Form', {
message:
'The form validation has failed, but no component with error was found',
rule: 'NO_UNDEFINED',
});

focusField(fieldWithError);
}, 0);
});
};
}, [focusFirstInvalidField, onSubmit]);

React.useImperativeHandle(ref, () => ({
focusFirstInvalidField,
focusFieldAt,
}));

return (
Expand Down

0 comments on commit 7eb9f09

Please sign in to comment.