HotKey to trigger Ant Design Select to begin search - javascript

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 }

Related

Trigger api call on setting the default value of MUI Select

In Short : Execute the onChange event when default value is set in Material-UI Select Component.
When you will run this react component, you will find a Material UI Select Component with a defaut selected option as 10 which is being set using useState hook.How to make an API call at the same time when the default value is being set.
The onChange props will only execute when we change the dropdown menu item.If we are loading our page with some Select Component and setting some default menu item, then how can we make an API call and get some Data.
Use the CodeSand Box link. The alert message does not executes even though the menu-item/ option in the Select is getting changed from empty string to 10.
Assume that the default value is coming from some API call.
export default function SelectVariants() {
const [age, setAge] = React.useState("10");
//This should execute by default or on Page Load
const handleChange = (event: SelectChangeEvent) => {
setAge(event.target.value);
alert("Run on load"); // Can be an API Call on the Page load And also on Subsequent onChange Events
};
return (
<div>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="demo-simple-select-standard-label">Age</InputLabel>
<Select
labelId="demo-simple-select-standard-label"
id="demo-simple-select-standard"
value={age}
onChange={handleChange}
label="Age"
>
<MenuItem value={""}>None</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</div>
);
}
Code Pen Example
Following this requirement:
Set a default value on the Select which is coming from an API call
(API-1) -> then using the same default value, trigger another API call
(API-2) -> then at every onChange of Select menu, also make API call
(API-2) only.
const [age, setAge] = React.useState("");
useEffect(() => {
api1().then((defaultValue) => {
setAge(defaultValue);
api2(defaultValue);
});
}, []);
const handleChange = (event) => {
setAge(event.target.value);
api2(event.target.value);
};
Working example
Hope this helps.

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>

Load Material UI AutoComplete suggestions after user input

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.

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.

How can I make this react input component idiomatic?

Here's a react input component:
function Input({ value, setValue }) {
return (
<div>
<input value={value} onChange={event => setValue(event.target.value)} />
<button onClick={() => setValue(value.toUpperCase())}>Capitalize</button>
</div>
);
}
It's just a vanilla input component together with a button that capitalizes the input's value. It's meant to be controlled by some parent component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} setValue={setValue} />;
}
This works fine, but it's not idiomatic. To be useable as a "drop-in replacement" for a vanilla input component, it should take an onChange prop, not setValue, the relevant difference being that onChange takes a synthetic event as an argument while setValue takes a string. (I'd like the presence of the capitalize button to be "opaque" to a developer using this Input component.)
I tried to idiomaticize this (see snippet below) by having the input element fire off a change event when the button is clicked, but the this doesn't cause onChange to execute. (I assume that this is due to details of react's synthetic event system that I don't understand. I browsed a bunch of posts on this topic, but couldn't get the ideas I found to work.)
function AnotherInput({ value, onChange }) {
let input = useRef();
let handleClick = () => {
input.current.value = value.toUpperCase();
var event = new Event("change" /* also tried "input" */, {
bubbles: true
});
input.current.dispatchEvent(event); // onChange doesn't fire!
};
return (
<div>
<input value={value} ref={input} onChange={onChange} />
<button onClick={handleClick}>Capitalize</button>
</div>
);
}
Also, I feel I shouldn't have to use a ref here because I don't want to modify the DOM directly; I just want to change the value in the controlling parent component.
Here's a CodePen.
I made it work by simulating the event Object on the Capitalize Button.
Parent Component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Input Component:
EDITED: I've managed to came up with a more elegant solution to the Input Component:
function Input({ value, onChange: inheritedOnChange }) {
return (
<div>
<input value={value} onChange={inheritedOnChange} />
<button value={value.toUpperCase()} onClick={inheritedOnChange}>Capitalize</button>
</div>
);
}
Note that i renamed the onChange prop to inheritedOnChange just for readability purposes. Preserving the onChange name at the destructuring should still work.

Categories