Load Material UI AutoComplete suggestions after user input - javascript

I have an Autocomplete component that is required to load a massive data list (up to 6000 elements) and show suggestions accordingly to the user's input.
As the data options have so many elements, whenever the user starts typing in a slow computer, it slows down and requires some time to load everything. I have to prevent it, so I came with an idea to show the user suggestions after they typed the third character. It's even giving me this error whenever the user clicks on the input box:
Warning: React instrumentation encountered an error: RangeError: Maximum call stack size exceeded console.
I need to show the suggestions after the third character input. I have tried to use the getOptionDisabled suggestion and the limitTags, but they did not work.
Here is the code:
const NameSelect = (props) => {
return (
<Dialog>
<React.Fragment>
<DialogTitle id="search-name-dialog-title">
Search name
</DialogTitle>
<DialogContent>
<Autocomplete
id="combo-box-client-select"
options={props.NameList}
value={props.preSelectedName}
getOptionLabel={(option) =>
option.Name_name +
", " +
option.country +
", " +
option.city
}
onChange={(object, value) => {
props.preSelectedNameSet(value);
}}
renderInput={(params) => (
<TextField
{...params}
label="Search name"
variant="outlined"
fullWidth
/>
)}
/>
.
.
.
</Dialog>
);
};
Can someone please help me with that approach, or suggest a better one? Thanks!

Try something like this:
<Autocomplete
inputValue={inputValue}
onInputChange={(e) => setinputValue(event.target.value)}
id="combo-box-demo"
options={values}
getOptionLabel={(option) => option}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
open={inputValue.length > 2}
/>
Use InputValue prop to trigger the auto complete drop down.
Example : auto complete dropdown

My idea is to add a state for Autocomplete current value to watch for its autoComplete property. That state will look something like this:
const [currentValue, useCurrentValue] = useState(props.preSelectedName);
so that your component will look something like this:
<Autocomplete
autoComplete={currentValue.length >= 3 ? true : false}
onChange={useCurrentValue}
...your other properties
/>
Another idea: you might wanna use setTimeout in your onChange method to slow down the re-render. But don't forget to clearTimeout before you set them.

The feature that you require is known as "Debouncing" and it is used whenever time consuming tasks occur frequently. In your case it, everytime you type the key, the suggestions are computed and this will definetely lead to lagging.
Lodash's debounce function will implement this for you.
As far as my knowledge, I am not sure whether you can implement this with MUI Autocomplete, but a custom solution you can do something like this:-
import React, { useState, useCallback } from "react";
import { _ } from "lodash";
function AutoComplete() {
const [input, setInput] = useState("");
const [suggestions, setSuggestions] = useState([]);
const updateInput = (input) => {
setInput(input);
/*_.debounce will fire the setSuggestions
and fetchSuggestions only after a gap of 3000ms */
_.debounce((input) => setSuggestions(fetchSuggestions(input), 3000));
};
return (
<div>
<input
value={input}
class="input"
onChange={(event) => updateInput(event.target.value)}
/>
<div class="suggestions">
<ul>
{suggestions?.length > 0 &&
suggestions?.map((val, idx) => (
<li class="suggestion" key={idx}>
{val}
</li>
))}
</ul>
</div>
</div>
);
}
export default AutoComplete;
You can style the components using the appropriate styles and materialize.css so that you get a functional replica of the Autocomplete component of MUI.

Related

Is it possible for my custom input not to work in shadow-dom?

I have a very simple react component with input:
const Search = ({ onchange, t }) => {
const [value, setValue] = useState('');
useEffect(() => {
console.log('value', value);
}, [value]);
return (
<div className={'user-search'}>
<input
placeholder={t('placeholder')}
className={'search-input'}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<div className={'search-icon'}>
<icon className={'icon'} size={'normal'} name="search" />
</div>
</div>
);
};
At the same time, I use a library with client components - most of them create a shadow-root in the DOM. Nevertheless, I can drop my components inside them via {children} or sometimes props.
Well, and I have a problem with this Search component: when I use it loosely anywhere in the project I get a console.log with "value" after typing something in the input. On the other hand, when I put it into the component with modal and accordion (from the client library) - input completely stops working and doesn't respond.
Could the non-functioning input have something to do with shadow-dom? Or do I need to provide more information?

React isn't updating a variable from a function

I'm using axios to return a map to my main app where it will be distributed to other values in the program. I am having an issue though. When I use 'onClick' on a drop down select, I want it to call that external function to return the JSON string and save it to a variable but it won't do it. I have console logged it and it says my variable is use undefined. Here is my axios code
import axios from "axios";
// ** when you launch server. Make sure 'express stuff' server is working and that it is posting to 5000/loadCycle
function Parent() {
let data = null;
console.log("called");
const url = "http://localhost:5000/";
axios
.get(`${url}loadCycle`)
.then((response) => {
data = response.data.workflow;
data = JSON.stringify(data);
data = JSON.parse(data);
//console.log(data);
const map = new Map(Object.entries(data));
console.log(map);
return map;
})
.catch((error) => console.error(`Error: ${error}`));
}
export default Parent;
and here is the code I want to format
function App() {
let dataCollection = null;
return (
<div>
<Box
sx={{ display: "flex", width: "40%", justifyContent: "space-between" }}
>
<Box sx={{ display: "flex" }}>
{/* <Typography sx={{ paddingTop: "6%" }}>Cycle</Typography> */}
{/* Cycle dropdown menu */}
{/* // MAKE CHANGES ON BRANCH // */}
<FormControl
sx={{ m: 1, minWidth: 200 }}
size="small"
variant="standard"
>
<InputLabel>Cycles</InputLabel>
<Select>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10} onClick={dataCollection=Parent()}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
{/* cycle dropdown menu end */}
</Box>
</div>
)
Why won't selecting 'one' from my select update dataCollection from 'null' to the map I am trying to return to it. Console logging it shows that the 'map' data in Parent is correct but the log for dataCollection is 'undefined'
<MenuItem value={10} onClick={dataCollection=Parent()}>Ten</MenuItem>
First of all you didn't define function (you are tried to do it like in a vainilla js, but react don't work in this way)
So, let's define separate function:
const handleSave = () => {
dataCollection=Parent()
}
// ...
<MenuItem value={10} onClick={handleSave}>Ten</MenuItem>
Next problem that's what your Parent function isn't synchronous, you should return your axios promise and after that use this function like that:
Parent().then(data => {
dataCollection = data;
})
That's not all, we can't save data at dataCollection, because your functional component this is like render function and you will lose your data on next render, so you shoud save your data to ref or state (depending on the purpose of use), let's use state:
const [dataCollection, setDataCollection] = React.useState();
// ...
const handleSave = () => {
Parent().then(data => {
setDataCollection(data);
})
}
Besides this I can see some style issues. And looks like you haven't read react doc attentively, please read againg "state and props" and "lifecycle" subjects from docs.
You have a couple of issues with your approach. I'm not sure what the other components: Box, FormControl, InputLabel, Select, and MenuItem are doing, so it makes it harder to discern if they are functioning correctly. I would simplify the code and just use regular HTML select and option tags. The select tag receives a change event and with React all events can be prefixed with "on", so it would be onChange on the select tag.
Create a prototype, a simpler project, that just focuses on that functionality until you understand it for your needs. Also, practice naming constructs a bit better, as Parent doesn't convey what it is doing. Aim to be succinct and general.

Input unfocus after typing something in it, i put an onChange function that sets a state using setstate(react hooks) (TextField material ui)

When using a setting a state((useState() / setState()), the input unfocuses, idk why?
pls help
React hook State
const [username, setUsername] = useState("");
this is the my own component which returns a textfield but with my own styles
function FormInput({ onChange, ...rest }) {
const classes = formInputStyles();
return (
<div>
<TextField
onChange={(e) => onChange(e.target.value)}
InputProps={{ classes, disableUnderline: true }}
{...rest}
/>
</div>
);
}
Like, i said in the title whenever i type something in the input field it unfocuses and clears the input field.
It dosen't unfocus when i use the normal < TextField > from material ui.
i tried making a whole new function for setting state but that didnt work
<FormInput
onChange={(value) => {
setUsername(value);
}}
label="Username"
variant="filled"
></FormInput>

React autocomplete component, call endpoint every time a letter is typed

I am working with react and the component autocomplete of material-ui, I need help with the following problem.
In the examples of autocomplete I saw you need to have all elements of the list in the frontend to use the autocomplete, in my case I get the list from a web service and it could be huge, so instead of searching for the whole list I want that every time a letter is typed in the autocomplete it generates a search to the web service filtering names according to the input that is being written and with a max results of 10 elements. The endpoint of the webservice already has a filter property where you can pass the quantity of results you want and the letters you want of the name.The only thing that the autocomplete has to do is everytime you type a letter it hits the endpoint (filtering with the word that is being typed) and updates the list of elements of the autocomplete.
Right now I have the following code, the problem is that it searches the whole list when you click the autocomplete but when you type each letter it doesn't do anything.
import Autocomplete from '#material-ui/lab/Autocomplete';
import TextField from '#material-ui/core/TextField';
import CircularProgress from '#material-ui/core/CircularProgress';
const [open, setOpen] = React.useState(false);
const [organizationList, setOrganizationList] = React.useState([]);
const loading = open && organizationList.length === 0;
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
if (active) {
try {
setOrganizationList(await api.post('/v1/organizations/search', {maxResults:10}));
} catch (error) {
snackbar.showMessage(error, "error");
}
}
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOrganizationList([]);
}
}, [open]);
The definition of the autocomplete:
<Autocomplete
id="asynchronous-demo"
style={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionSelected={(option, value) => option.orgName === value.orgName}
getOptionLabel={(option) => option.orgName}
options={organizationList}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
To hit the endpoint I have this:
setOrganizationList(await api.post('/v1/organizations/search', {maxResults:10}));
I need to pass the input of the autocomplete every time a letter is typed, like this:
setOrganizationList(await api.post('/v1/organizations/search', {name:inputAutocomplete,maxResults:10}));
Thanks a lot for the help.
Im new to react by the way.
In material-ui library Autocomplete component has a props onChange that can be used like this.
onChange={(event, newValue) => {
setValue(newValue);
}}
You should be interested in the second parameter newValue. Thus, you will receive a new input value every time a letter is typed.
Therefore, just move the logic for getting the list into this callback.
You can read more about controllable state in the material-ui documentation
Implementing the onChange function and giving it the function you already made should give you the solution you want.

HotKey to trigger Ant Design Select to begin search

I am using 'react-hotkeys-hook' and using my hotkey works (can see log in console through onFocus()). Goal is to have a hotkey which opens and adds the cursor to the Select component. (Using ant design - 'antd')
The issue I have is when I trigger the hotkey no event is passed to the onFocus obviously - how do I pass to onFocus the event so that it works just the same as if I were to click on it? Or should I go about this a different way. I have tried using the open={true or false} inside the Select component and this works but does not add the cursor!
useHotkeys('shift+p', () => onFocus());
const hotKeyOpenSearch = () => {
// pass searchbar event here?
onFocus()
console.log('hotkey shift+p')
}
const onFocus = (e) => {
console.log(e)
}
return (
<Select
showSearch
style={{ width: searchW, paddingLeft: searchP }}
dropdownStyle={{ zIndex: 9999 }}
placeholder="🔎 Fuzzy Search"
optionFilterProp="children"
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
>
</Select>
)
You are on the right track, but onFocus is for receiving events only; you cannot call that method in any way that is going to programmatically re-focus the select component. However, there is a .focus() method exposed by antd that you can call with your hotkey, but it requires getting a reference to the mounted component. Here is a working solution:
import React, { useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Select } from "antd";
export default function App() {
useHotkeys('shift+p', () => {
// kind of hackish, but without timeout,
// the hotkey will be sent to select input
setTimeout(() => {
selectRef.current.focus();
}, 20);
});
// This will hold reference to `<Select>`
const selectRef = useRef(null);
return (
<div className="App">
<Select
showSearch
style={{ width: 200, paddingLeft: 10 }}
dropdownStyle={{ zIndex: 9999 }}
placeholder="🔎 Fuzzy Search"
optionFilterProp="children"
ref={selectRef}
showAction={['focus']}
autoFocus={true}
>
<Select.Option key="blue">Blue</Select.Option>
<Select.Option key="red">Red</Select.Option>
</Select>
</div>
);
}
You'll notice that, in addition to using a ref I had to make one other significant change - changing two arguments on the <Select> component:
showAction={['focus']}
autoFocus={true}
This is because without them, it will move the cursor, but will fail to trigger the dropdown and the "fancy" select menu; see issue #8269 for details.
Sandbox Link
codesandbox.io/s/stackoverflow-65071488-t89q0
For people who want to open the dropdown on focus, use the onFocus & onBlur methods to set/unset a key in the state and use that key as the value for autoFocus.
onFocus = {() => this.setState({ isSelectFocussed: true })}
onBlur = {() => this.setState({ isSelectFocussed: false })}
showAction = "focus"
autoFocus = { this.state.isSelectFocussed }

Categories