Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(SearchBox): expose formRef (#3565)
Browse files Browse the repository at this point in the history
<!--
  Thanks for submitting a pull request!
Please provide enough information so that others can review your pull
request.
-->

**Summary**
This change is motivated from a very specific use case:

I have a custom form input component which includes a `SearchBox`, so I
my own `<form>` tag is wrapping the `<form>` that's within `SearchBox`.
This leads to an unexpected form submission when pressing the
<kbd>Enter</kbd> key in the search box text input.

I've already tried `preventDefault` and `stopPropagation` in the
`onSubmit` callback available for `SearchBox` but it seems the way React
handles events makes this useless. Getting a `ref` for the form allowed
me to stop the submit event by using `addEventListener` on the
`ref.current` element.
 
Since there's already an `inputRef` I think having a `formRef` won't
hurt and would help others that may find themselves in a similar form
nesting situation.

**Result**
A new `formRef` prop analog to current `inputRef` is added to
`SearchBox`.
  • Loading branch information
esaborit4code authored Sep 26, 2022
1 parent 894e261 commit 1c2f46d
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 1 deletion.
6 changes: 6 additions & 0 deletions packages/react-instantsearch-dom/src/components/SearchBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class SearchBox extends Component {
isSearchStalled: PropTypes.bool,
showLoadingIndicator: PropTypes.bool,

formRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.exact({ current: PropTypes.object }),
]),

inputRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.exact({ current: PropTypes.object }),
Expand Down Expand Up @@ -249,6 +254,7 @@ class SearchBox extends Component {
return (
<div className={classNames(cx(''), className)}>
<form
ref={this.props.formRef}
noValidate
onSubmit={this.props.onSubmit ? this.props.onSubmit : this.onSubmit}
onReset={this.onReset}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ describe('SearchBox', () => {
expect(inputRef.current.tagName).toEqual('INPUT');
});

it('should return a ref when given a callback', () => {
it('should return a ref when given a callback as input ref', () => {
let inputRef;
const wrapper = mount(
<SearchBox
Expand All @@ -338,4 +338,25 @@ describe('SearchBox', () => {
expect(refSpy).toHaveBeenCalled();
expect(inputRef.tagName).toEqual('INPUT');
});

it('should point created refs to its form element', () => {
const formRef = React.createRef();
mount(<SearchBox refine={() => null} formRef={formRef} />);

expect(formRef.current.tagName).toEqual('FORM');
});

it('should return a ref when given a callback as form ref', () => {
let formRef;
mount(
<SearchBox
refine={() => null}
formRef={(ref) => {
formRef = ref;
}}
/>
);

expect(formRef.tagName).toEqual('FORM');
});
});
3 changes: 3 additions & 0 deletions packages/react-instantsearch-hooks-web/src/ui/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export type SearchBoxProps = Omit<
React.ComponentProps<'input'>,
'placeholder' | 'onChange' | 'autoFocus'
> & {
formRef?: React.RefObject<HTMLFormElement>;
inputRef: React.RefObject<HTMLInputElement>;
isSearchStalled: boolean;
value: string;
Expand Down Expand Up @@ -131,6 +132,7 @@ function DefaultLoadingIcon({ classNames }: IconProps) {
}

export function SearchBox({
formRef,
inputRef,
isSearchStalled,
onChange,
Expand Down Expand Up @@ -176,6 +178,7 @@ export function SearchBox({
className={cx('ais-SearchBox', classNames.root, props.className)}
>
<form
ref={formRef}
action=""
className={cx('ais-SearchBox-form', classNames.form)}
noValidate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('SearchBox', () => {
const onSubmit = jest.fn();

return {
formRef: createRef<HTMLFormElement>(),
inputRef: createRef<HTMLInputElement>(),
isSearchStalled: false,
onChange,
Expand Down Expand Up @@ -383,6 +384,29 @@ describe('SearchBox', () => {
expect(props.onSubmit).toHaveBeenCalledTimes(1);
});

test('triggers `submit` event on current form when pressing `Enter` on the input element', () => {
const formRef = createRef<HTMLFormElement>();
const props = createProps({ formRef });

const { container } = render(<SearchBox {...props} />);

const form = formRef.current;
const listenerSpy = jest.fn();
if (form) {
form.addEventListener('submit', listenerSpy);
}

const input = container.querySelector<HTMLInputElement>(
'.ais-SearchBox-input'
)!;

userEvent.type(input, 'query');
expect(listenerSpy).toHaveBeenCalledTimes(0);

userEvent.type(input, '{enter}');
expect(listenerSpy).toHaveBeenCalledTimes(1);
});

test('blurs input onSubmit', () => {
const props = createProps({});

Expand Down

0 comments on commit 1c2f46d

Please sign in to comment.