I am currently tasked with building a React component that is able to use/populate a dropdown with hundred of thousands of options. Of course we don’t want to retrieve all records at once from the server but only serve what the user is searching for.
I have created a reusable component, hooked into Formik (Link), that you can use to get paginated dropdown data from your API. I have used the ‘react-select-async-paginate’ package, find more information here.
Our API (a GET) returns this JSON:
{
"messages": [
"string"
],
"succeeded": true,
"data": {
"items": [
{
"value": 0,
"label": "string"
}
],
"totalCount": 0
}
}
And has the following input properties:
Keyword
SkipCount
MaxResultCount
Keyword is used for searching
SkipCount used to skip X amount of records on the server
MaxResultCount is to specify how many records you want to return at once from the API. We use a default of 30.
We have built the following React Component:
import { InputLabel, FormHelperText } from '@mui/material'
import FormControl from '@mui/material/FormControl'
import { FormikContextType, useFormikContext } from 'formik'
import { AsyncPaginate } from 'react-select-async-paginate'
import { ReactNode } from 'react'
import * as _ from 'lodash'
import { DropdownValue } from 'src/types/common'
interface AppSelectInputProps {
name: string
selectedValuesKey?: string
label?: string
isMultiple?: boolean
getdata?: any
[x: string]: any
}
const AppSelectInput: React.FC<AppSelectInputProps> = ({
name,
selectedValuesKey,
label,
getdata = null,
isMultiple = false,
...otherProps
}) => {
const { setFieldTouched, setFieldValue, values, errors, touched }: FormikContextType<any> = useFormikContext()
const onChange = (event: any) => {
if (isMultiple) {
setFieldValue(
name,
event.map(function (item: any) {
return item.value
})
)
if (selectedValuesKey) {
setFieldValue(selectedValuesKey, event.option)
}
} else {
setFieldValue(name, event.value)
if (selectedValuesKey) {
setFieldValue(selectedValuesKey, event)
}
}
}
const loadPageOptions = async (q: any, prevOptions: any, { take, skip }: any) => {
const data = await getdata({
keyword: q || '',
maxResultCount: take,
skipCount: skip
})
.unwrap()
.then((res: any) => res.data)
return {
options: data.items.map((item: DropdownValue) => {
return {
value: item.value,
label: item.name || item.label
}
}),
hasMore: skip < data.totalCount,
additional: {
skip: skip + 30,
take: take
}
}
}
const defaultAdditional = {
skip: 0,
take: 30
}
return (
<div>
{label && <InputLabel sx={{ fontSize: 14 }}>{label}</InputLabel>}
<FormControl fullWidth error={(_.get(errors, name) && _.get(touched, name)) as boolean}>
<AsyncPaginate
className='react-select'
placeholder='Selecteer...'
isMulti={isMultiple}
onChange={onChange}
value={_.get(values, selectedValuesKey || '')}
loadOptions={loadPageOptions as any}
onBlur={() => setFieldTouched(name)}
additional={defaultAdditional}
{...otherProps}
/>
</FormControl>
{_.get(touched, name) && _.get(errors, name) && (
<FormHelperText sx={{ fontSize: 14 }} error={true}>
{_.get(errors, name) as ReactNode}
</FormHelperText>
)}
</div>
)
}
export default AppSelectInput
And use it like this inside your Formik component:
<AppSelectInput
getdata={getRegistrationStatuses as any}
selectedValuesKey='registrationStatus'
name='statusId'
isMultiple={false}
/>
Our Initialvalues object looks like this:
const initialValues = {
statusId: 0,
registrationStatus: { value: 1, label: 'This is a test!' }
}
So, whenever we want to render selected values in the dropdown, we use the ‘selectedValuesKey’ specified when rendering the AppSelectInput component. In our example above that is the registrationStatus key. This is awesome when you want to create a ‘Edit’ form and load the previously selected value or values.
Our API call to the endpoint to render data is done with RTK-Query and is quite simple:
getRegistrationStatusesForDropdown: builder.query<Dropdown, BaseDropdownSearchParams>({
query: params => ({
url: '/v1/RegistrationStatus/Dropdown',
method: 'GET',
params: params
}),
providesTags: ['Status']
}),
The interface Dropdown and BaseDropDownSearchParams look like this:
export interface BaseDropdownSearchParams {
keyword: string
skipCount: number
maxResultCount: number
}
export interface Dropdown {
messages: string[]
succeeded: boolean
data: {
items: {
label: string
value: string
}[]
}
}
And thats it, a reusable component for server sided dropdowns!