Reputation: 331
I wish to create a autocomplete search bar with my own custom call to the backend, which searches through a list of tickers.
<Autocomplete
multiple
id="checkboxes-tags-demo"
options={watchlistSearchTickers}
disableCloseOnSelect
getOptionLabel={(option: any) => (option!) ? option.Symbol : null}
renderOption={(props, option, { selected }) => (
<li {...props}>
{option.Symbol}
</li>
)}
style={{ padding: 0 }}
onChange={(event, query: any) => handleWatchlistSearch(query)}
filterOptions={(x) => x}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<input type="text" {...params.inputProps} />
</div>
)}
/>
The initial render here seems fine, but on click the text input box, an error "options.filter" is not a function
occurs. Here is the function that calls the backend through a post request:
const [watchlistSearchTickers, setWatchlistSearchTickers] = useState<Array<watchlistSearchInterface>>([])
function handleWatchlistSearch(query: string) {
axiosInstance.post("/portfolio/watchlist/search/", {
query: query
}).then((res) => {
console.log(res)
setWatchlistSearchTickers(res.data)
})
}
useEffect(() => {
handleWatchlistSearch("")
}, []) // Initialize with empty list of tickers
Does anyone know why this happens?
Upvotes: 10
Views: 10977
Reputation: 11
th3oll is correct in that Load on Open solves this nicely. Some tips:
To get Load on Open working, you basically put your state and useEffect logic into the handleOpen() function placed inside a component, as mentioned in the docs. The call in turn is the "effect" of the <Autocomplete>
opening
Ex. as it worked for me:
const SomeComponent = () => {
const [open, setOpen] = useState(false);
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const handleOpen = () => {
setOpen(true);
const makeBackEndCall = async () => {
setLoading(true)
const response = await fetch(`your.endpoint.com/data`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
// etc
},
});
setLoading(false)
return await response.json()
}
makeBackEndCall().then(data => setOptions(data))
}
const handleClose = () => {
//should be able to copy paste
}
return (
<Autocomplete /> //copy same from docs
)
};
Then depending on the format of your data coming back, you may have to either morph the data to fit the options structure or use isOptionEqualtoValue
or getOptionsLabel
props to process your data. For me, I simply ended up having to delete the getOptionsLabel prop. The docs say options
prop should accept a simple array of strings out of the box, but that did not work for me.
The way the docs show the placeholder async function makes it look a bit confusing, but everything else is just copy/paste
Hope this helps! --erre
Upvotes: 1
Reputation: 106
So this bug actually happens when you pass null
or undefined
values into the options
prop of the Autocomplete component. This is likely happening because you're clicking in the Autocomplete "too fast", so before it can get the results from your API call, it passes a value that the component tries to .filter()
it, causing the error.
I was also having this issue, and I managed to figure a workaround that works (it's not perfect but it stops your code from crashing).
<Autocomplete
multiple
id="checkboxes-tags-demo"
options={!watchlistSearchTickers ? [{label:"Loading...", id:0}] : watchlistSearchTickers }
(...)
With this ternary, what we do is that while your API is working on your request, it puts a placeholder "Loading..." option so it won't crash your code (Similar-ish to the Load on open prop, but I never figured how to make that work).
For me the solution above works well, but the fanciest and best choice would be the Load prop that is something from MUI.
Hope I could help!
Edit: Actually, if you can initialize your options to an empty array [], you can set the loading prop as true, which will display loading while your content loads!
Upvotes: 5