Skip to content

Commit

Permalink
Fix form field not being focused (#80)
Browse files Browse the repository at this point in the history
* fix: form field loosing focus

* fix: form field loosing focus
  • Loading branch information
ceceppa authored Dec 12, 2022
1 parent 278f177 commit 7459f76
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 6 deletions.
3 changes: 2 additions & 1 deletion lib/internal/checks/checkFocusTrap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type React from 'react';
import type { TextInput } from 'react-native';

import { isFocused } from '../isFocused';
import type { LogParams } from '../logger';

export type CheckFocusTrap = {
Expand All @@ -13,7 +14,7 @@ export const checkFocusTrap = async ({
}: CheckFocusTrap): Promise<LogParams | null> => {
return new Promise(resolve => {
setTimeout(() => {
const hasFocus = ref?.current?.isFocused() === shouldHaveFocus;
const hasFocus = isFocused(ref?.current) === shouldHaveFocus;

if (!hasFocus) {
const message = shouldHaveFocus
Expand Down
5 changes: 5 additions & 0 deletions lib/internal/isFocused.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { TextInput } from 'react-native';

export const isFocused = (input: TextInput | undefined | null) => {
return input?.isFocused();
};
53 changes: 52 additions & 1 deletion lib/providers/Form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Form', () => {
expect(setFocus).toHaveBeenCalledWith('whatever');
});

it('calls the .focus callback if the next field has it and is editable', () => {
it('calls the .focus callback if the next field has it and is editable', async () => {
const focus = jest.fn();
const ref = { current: { focus, isFocused: jest.fn() } };
const secondField = {
Expand All @@ -108,7 +108,58 @@ describe('Form', () => {

providerValues.focusField?.(secondField);

await waitFor(() => expect(focus).toHaveBeenCalledWith());
});

it('awaits 50ms before triggering again the `focus` event if the component already have the focus', async () => {
jest.useFakeTimers();

const focus = jest.fn();
const ref = {
current: { focus, isFocused: jest.fn().mockReturnValue(true) },
};
const secondField = {
ref,
hasFocusCallback: true,
hasValidation: false,
isEditable: true,
id: 'second',
};

let providerValues = customRender();

providerValues.refs?.push({
ref: { current: 'random ref' },
hasFocusCallback: false,
hasValidation: false,
id: 'first',
});

// next field
providerValues.refs?.push(secondField);

providerValues.focusField?.(secondField);

expect(focus).not.toHaveBeenCalled();
expect(checkFocusTrap).not.toHaveBeenCalled();

jest.advanceTimersByTime(51);

expect(focus).toHaveBeenCalledWith();

await waitFor(() =>
expect(checkFocusTrap).toHaveBeenCalledWith({
ref: {
current: {
focus: expect.any(Function),
isFocused: expect.any(Function),
},
},
shouldHaveFocus: true,
}),
);

jest.useRealTimers();
});

it('calls the setFocus callback if the next field is not editable', () => {
Expand Down
21 changes: 17 additions & 4 deletions lib/providers/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { InteractionManager } from 'react-native';

import { useFocus } from '../hooks/useFocus';
import { isFocused } from '../internal/isFocused';
import { useChecks } from '../internal/useChecks';

export type FormProps = React.PropsWithChildren<{
Expand Down Expand Up @@ -35,12 +36,24 @@ export const Form = ({ children, onSubmit }: FormProps) => {
'No next field found. Make sure you wrapped your form inside the <Form /> component',
rule: 'NO_UNDEFINED',
});

if (callFocus) {
nextRefElement?.current?.focus();
/**
* 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 });
__DEV__ &&
nextRefElement &&
checks?.checkFocusTrap({
ref: nextRefElement,
shouldHaveFocus: true,
});
}, timeoutValue);
} else if (nextRefElement?.current) {
setFocus(nextRefElement?.current);
}
Expand Down

0 comments on commit 7459f76

Please sign in to comment.