I'm trying to build a generic autocomplete component with Material UI autocomplete component, react-hook-form and my custom hooks.
What it should do: on user input (after every letter) there should be a call to api, the list of options in autocomplete should be updated accordingly.
What is my issue: many rerenders of child and parent components (also when user's typing or deleting fast there's some lagging most likely connected with rerenders). I think there should be a way to avoid this behaviour using react-hook-form, which I'm trying to use since the autocompletes are form fields.
Can someone tell me is my approach wrong? I can't really change much the hook cointaining a call to api. Maybe someone had a similar issue already? I tried wrapping mui autocomplete with controller from react-hook-form but it didn't help much. Location component is being rerendered like 130 times when user types in what he wants and deletes it. Any ideas how to make this work?
What it currently looks like:
Form component (with RHF provider for context)
import React from 'react';
import { useForm, FormProvider } from 'react-hook-form';
export default function UserForm(): JSX.Element {
const methods = useForm();
const handleSkip = (): void => {
console.log('skipped');
};
const onSubmit = useCallback((rawFormData) => {
console.log(rawFormData);
}, []);
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Location />
<Actions>
<SubmitButton isPending={false}>Submit</SubmitButton>
</Actions>
</Form>
</FormProvider>
);
}
Locations:
import React, { useState } from 'react';
import { useFormContext } from 'react-hook-form';
export default function Location(): JSX.Element {
const { register } = useFormContext();
const [selectedCountry, setSelectedCountry] = useState(null);
const [nameOfCountry, setNameOfCountry] = useState('');
const { data: countries} = useAutocomplete(nameOfCountry);
return (
<div>
<CustomAutocomplete
autocompleteOptions={countries}
name='country'
onInputChange={setSelectedCountry}
placeholder='country'
registerInput={register()}
/>
</div>
);
}
CustomAutocomplete:
import React, { SetStateAction, Dispatch, Ref, useState, useEffect } from 'react';
import { TextField } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { useFormContext, Controller } from 'react-hook-form';
interface Properties {
name: string;
placeholder: string;
autocompleteOptions: string[];
onInputChange?: Dispatch<SetStateAction<string>>;
onChange?: Dispatch<SetStateAction<string>>;
isLoading?: boolean;
registerInput: Ref<any>;
}
CustomAutocomplete.defaultProps = {
isLoading: false,
onChange: (value: Record<string, never>) => value,
onInputChange: (value: string) => value,
};
export default function CustomAutocomplete({
name,
placeholder,
autocompleteOptions,
onInputChange,
isLoading,
onChange,
registerInput,
}: Properties): JSX.Element {
const { errors } = useFormContext();
const options = autocompleteOptions || [];
return (
<div>
<Autocomplete
freeSolo
getOptionLabel={(option: Country | string) => option.name || option}
id="input-autocomplete"
onInputChange={(event, value) => {
onInputChange(value);
}}
openOnFocus={false}
// onChange={(event, value) => onChange && onChange(value) }
options={options}
renderInput={(parameters) => (
<TextField
{...parameters}
error={Boolean(errors && errors[name])}
helperText={errors && errors[name]?.message}
id={name}
inputRef={registerInput}
name={name}
placeholder={placeholder}
rowsMax="1"
/>
)}
/>
</div>
);
}
useAutocomplete hook:
Contains a call to api an returns object like:
{
//array of resuls
data: [],
isLoading: boolean
}