Need help capturing the user choice from Autocomplete (multiple) in state (React) - reactjs

Thanks for reading. I'm using Material-UI Autocomplete and trying to figure out how to capture a user choice. Here's the code. For now, I'm trying to console.log the user choice or deletion of a movie from the selection list. If I can get this to work I'll probably use a state settor in handleChange to capture the movie and read the state value for processing once the dialog is submitted. Since this is a multiple Autocomplete there may be several selected values.
Currently event.target.value in handleChange displays a zero when selecting a movie from the list, and undefined when removing. I need it to display the actual title.
Happy to consider any suggestion to get to the objective of being able to process all autocomplete selections upon dialog submission. Thanks in advance!
React version: 16.8

Resolved by sending the parameters (event, value) from onChange. Please see the onChange value in the below snippet.
export default function App() {
const classes = useStyles();
const handleChange = (value) =>
{
const x = value.map(function(a){return {title:a.title};});
console.log(x);
};
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-outlined"
options={top100Films}
onChange={(event, value) => handleChange(value)}
getOptionLabel={(option) => option.title}
defaultValue={[top100Films[14]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
</div>
);
}

Related

How to get the ID on the searched data autocomplete material UI react

Am working on this project using react js/Material UI
Now I want to get the ID of the data that I had searched. this is my code so far
<Autocomplete
id="free-solo-demo"
freeSolo
options={data.map((option) => option.item)}
renderInput={(params) => <TextField {...params} label="Search" />}
onChange={(event, value) => setSearch(value)}
/>
my database
In the above, I was able to store the searched data but what I really want is to get the ID only. Hope someone can point out what am missing here.
thanks
I think it would be better to convert data to what mui autocomplete can handle (array of objects with label and value properties)
so you can convert it in this way :
options={data.map(opt=>({label:opt.title,value:opt.id}))}
and add
getOptionLabel={(option) => {
return option.label;
}}
for only showing label for each option
and the value of the search state would be sth like this: {label:'The Game',value:1} so the id would be search.id
I believe if your author model includes all three fields and if you set the selection to that model, then you should be able to access the id from the selection.
not my code but on the onChange event here:
import React, { useState } from 'react';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import { nbaTeams } from './nbaTeams';
const NbaAutocomplete = () => {
const [selectedTeam, setSelectedTeam] = useState(null);
console.log(selectedTeam);
return (
<Autocomplete
id="nba teams"
options={nbaTeams}
renderInput={params => (
<TextField {...params} label="NBA Team" variant="outlined" />
)}
getOptionLabel={option => option.name}
style={{ width: 270 }}
value={selectedTeam}
onChange={(_event, newTeam) => {
setSelectedTeam(newTeam);
}}
/>
);
};
export default NbaAutocomplete;
Ref to the creator of that example / not me:
https://plainenglish.io/blog/how-to-use-the-autocomplete-component-in-material-ui-11a7132d2b71
You can also probably do it in options, by setting getOptionLabel to model->title and then parsing the model for the id from the selection model. Maybe useful example similar to your model: https://mui.com/joy-ui/react-autocomplete/

MUI Autocomplete and react-hook-form not displaying selected option with fetched data

I have a MUI Autocomplete inside a form from react hook form that works fine while filling the form, but when I want to show the form filled with fetched data, the MUI Autocomplete only displays the selected option after two renders.
I think it's something with useEffect and reset (from react hook form), because the Autocompletes whose options are static works fine, but the ones that I also have to fetch the options from my API only works properly after the second time the useEffect runs.
I can't reproduce a codesandbox because it's a large project that consumes a real api, but I can provide more information if needed. Thanks in advance if someone can help me with this.
The page where I choose an item to visualize inside the form:
const People: React.FC = () => {
const [show, setShow] = useState(false);
const [modalData, setModalData] = useState<PeopleProps>({} as PeopleProps);
async function showCustomer(id: string) {
await api
.get(`people/${id}`)
.then((response) => {
setModalData(response.data);
setShow(true);
})
.catch((error) => toast.error('Error')
)
}
return (
<>
{...} // there's a table here with items that onClick will fire showCustomer()
<Modal
data={modalData}
visible={show}
/>
</>
);
};
My form inside the Modal:
const Modal: React.FC<ModalProps> = ({data, visible}) => {
const [situations, setSituations] = useState<Options[]>([]);
const methods = useForm<PeopleProps>({defaultValues: data});
const {reset} = methods;
/* FETCH POSSIBLE SITUATIONS FROM API*/
useEffect(() => {
api
.get('situations')
.then((situation) => setSituations(situation.data.data))
.catch((error) => toast.error('Error'));
}, [visible]);
/* RESET FORM TO POPULATE WITH FETCHED DATA */
useEffect(() => reset(data), [visible]);
return (
<Dialog open={visible}>
<FormProvider {...methods}>
<DialogContent>
<ComboBox
name="situation_id"
label="Situação"
options={situations.map((item) => ({
id: item.id,
text: item.description
}))}
/>
</DialogContent>
</FormProvider>
</Dialog>
);
};
export default Modal;
ComboBox component:
const ComboBox: React.FC<ComboProps> = ({name, options, ...props}) => {
const {control, getValues} = useFormContext();
return (
<Controller
name={`${name}`}
control={control}
render={(props) => (
<Autocomplete
{...props}
options={options}
getOptionLabel={(option) => option.text}
getOptionSelected={(option, value) => option.id === value.id}
defaultValue={options.find(
(item) => item.id === getValues(`${name}`)
)}
renderInput={(params) => (
<TextField
variant="outlined"
{...props}
{...params}
/>
)}
onChange={(event, data) => {
props.field.onChange(data?.id);
}}
/>
)}
/>
);
};
export default ComboBox;
I think you simplify some things here:
render the <Modal /> component conditionally so you don't have to render it when you are not using it.
you shouldn't set the defaultValue for your <Autocomplete /> component as RHF will manage the state for you. So if you are resetting the form RHF will use that new value for this control.
it's much easier to just use one of the fetched options as the current/default value for the <Autocomplete /> - so instead of iterating over all your options every time a change is gonna happen (and passing situation_id as the value for this control), just find the default option after you fetched the situations and use this value to reset the form. In the CodeSandbox, i renamed your control from "situation_id" to "situation". This way you only have to map "situation_id" on the first render of <Modal /> and right before you would send the edited values to your api on save.
I made a small CodeSandbox trying to reproduce your use case, have a look:
mui#v4
mui#v5
Another important thing: you should use useFormContext only if you have deeply nested controls, otherwise just pass the control to your <ComboBox /> component. As with using FormProvider it could affect the performance of your app if the form gets bigger and complex. From the documentation:
React Hook Form's FormProvider is built upon React's Context API. It solves the problem where data is passed through the component tree without having to pass props down manually at every level. This also causes the component tree to trigger a re-render when React Hook Form triggers a state update

How to get the ref.current dom element when using react-select and react refs?

I have a group of react selects using the react-select package – https://react-select.com/home.
I have a component that wraps three react-selects – something basically like this:
import Select from "react-select"
function SelectGroup(){
const ref1 = useRef(null);
const ref2 = useRef(null);
const ref3 = useRef(null);
return (
<div>
<Select ref={ref1} />
<Select ref={ref2} />
<Select ref={ref3} />
</div>
)
}
I need to perform some checks to see what's in focus. The reason I am doing this is because the group of selects is a single component that needs to be able to allow the user to navigate in multiple ways through the keyboard. Spacebar, and enter keys should allow the user to shift the focus to the next select element. Arrow keys should allow the user to go to the next or previous select. So, focus needs to be managed somehow, and this means knowing what's currently in focus.
Normally, I would do that like this:
function isActiveElement(ref){
return ref?.current === document.activeElement
}
However, for ref.current react-select returns an object called StateManager – https://react-select.com/props#statemanager-props
So, ref.current === document.activeElement always returns false.
How, can I check to see which react-select is in focus? I was unable to find anything about this in the react-select docs. Maybe, I'm missing it? I have solved this problem others ways, but I was curious if there is a way to do it this "simpler way" I describe above, which may be the more common approach.
You can listen to the focus and the blur event to keep track of the currently focused Select:
export default function App() {
const [focus, setFocus] = useState(-1);
const onBlur = () => setFocus(-1);
return (
<div>
<div>Current focus: {focus}</div>
<Select
onFocus={() => setFocus(0)}
onBlur={onBlur}
options={colourOptions}
/>
<Select
onFocus={() => setFocus(1)}
onBlur={onBlur}
options={colourOptions}
/>
<Select
onFocus={() => setFocus(2)}
onBlur={onBlur}
options={colourOptions}
/>
</div>
);
};
Live Demo

Using onSelect property of materialUI autocomplete form

I'm trying to create a search form with autocomplete suggestions that pulls data from API. I got everything to work in terms of displaying data and selection, but I would like to automatically take the user to the related page once they select one of the suggestions.
<Autocomplete
id="search-input"
freeSolo
disableClearable
options={playerList}
getOptionLabel={(option) => option.nickname}
style={{ width: 200}}
**onClick={console.log("you clicked")}
onSelect={(val)=> window.location.href = "/player-statistics/"+val.nickname+"-"+val.account_id+"-"+server}**
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
if (newInputValue.length >= 3) {fetchPlayers(newInputValue)}
}}
renderInput={(params) => <TextField {...params} label="Search Players" variant="outlined" margin="normal" />}
/>
I have tried with onChange and onSelect, but they are both reloading the page continuously whenever you press the search field or start typing.
The idea is to skip the need to click "search" button.
When you select a suggested name, onInputChange is triggered.
There you need to put the logic for changing the location.
Then the user does not have to click anything else.
If you do not want the page to change when the user only types a single character,
then you need to write logic for that inside onInputChange.
sth. like
// inside onInputChange
if(isASuggestedName(newInputValue)) {
//...change location
}
I figured it out after going through the API.
onChange call has 3 parameters - function(event: object, value: T | T[], reason: string) => void
I basically set it up so relocation is triggered only if reason is select-option and it works perfectly.
Thank you everyone

Material-ui autocomplete clear value

I have one problem in my react code.
I use Material-ui and redux-form. I have select input like and after change this select i should reset value in . I use action 'change' from react-form and set value for textfield. But label in still remains. Can i clear or reset value in ?
<Autocomplete
options={list}
getOptionLabel={option => option.name}
onInputChange={onChange}
onChange={onChangeAutoComplete}
noOptionsText='Нет доступных вариантов'
loadingText='Загрузка...'
openText='Открыть'
renderInput={params => (
<Field
{...params}
label={label}
name={fieldName}
variant="outlined"
fullWidth
component={renderTextField}
className={classes.textField}
margin="normal"
/>
)}
/>
Using hooks on the value prop breaks the functionality of the autocomplete component ( at least for me ). Using class, and setting the local state is the same.
Luckily it is a react component, so it have a "key" prop. When the key prop changes, the component is re-rendered with the default values ( which is an empty array since nothing is selected). I used hooks in the parent component and passed the values to the key prop, whenever reset is needed.
<Autocomplete
key={somethingMeaningful} // Bool, or whatever just change it to re-render the component
//...other props
/>
Hope this helps!
Material UI Autocomplete onInputChange callback provides reason argument. If input has been changed by input, reason will be input and if you selected option then reason will be reset.
onInputChange={(event, newInputValue, reason) => {
if (reason === 'reset') {
setValue('')
return
} else {
setValue(newInputValue)
}
}}
setValue is useState and you can pass value state to autocomplete value property.
use value in your <Autocomplete /> like this:
<Autocomplete
value={this.state.value} //insert your state key here
//...other props
/>
Then clear state of that key, to clear the autocomplete field value
I am going to post a very dirty way of clearing the value of Autocomplete. Try it ONLY when nothing else works;
import React, { useRef } from 'react';
...
const autoC = useRef(null);
...
<Autocomplete
...
ref={autoC}
/>
and then when you want to clear the value;
const ele = autoC.current.getElementsByClassName('MuiAutocomplete-clearIndicator')[0];
if (ele) ele.click();
This is what worked for me.
const [name, setName] = useState('');
<Autocomplete
inputValue={name}
onChange={(e,v)=>setName(v?.name||v)}
...
/>
<Button onClick={()=>setName('')}>
Clear
</Button>
You can use something like the following to clear the autocomplete field when an item is selected.
<Autocomplete
value={null}
blurOnSelect={true} />
Note that you may also need to set clearOnBlur={true} if you're using the freeSolo option.
Source https://mui.com/api/autocomplete/#props
I achieved this by updating the inputValue prop where multiple prop is false. If you are using multiple prop, then there is a propblem (bug). Selected values does not get erased.
When I encountered this, it was when options for the autocomplete changed, and wanted to clear the input value. It wouldn't clear with just the options changing. What worked for me is adding a key value onto the autocomplete which depended on the change which necessitated clearing.
To solve this, I created a hook that watches the value state of the autocomplete and set the value of the input if the checkClear returns true;
function useAutocompleteInputClear(watch, checkClear) {
const elmRef = useRef(null);
useMemo(() => {
if (!elmRef || !elmRef.current) return;
if (!checkClear || typeof checkClear !== "function") return;
const button = elmRef.current.querySelector("button")
if (checkClear(watch) && button) {
button.click();
}
}, [watch])
return elmRef;
}
Its first argument is the value that should be watched and its second argument is a function that returns a boolean. if it is true the clearing will happen.
Also, the hook returns a ref that needs to pass as ref prop to Autocomplete.
const elmRef = useAutocompleteInputClear(value, v => !v || !v.id)
<Autocomplete ref={elmRef}
value={value}
...
using onChange property we can clear the value by clicking the clear icon in the following way
<Autocomplete
fullWidth={true}
label={'Source'}
margin={'noraml'}
multiple={false}
name={'Source'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
ref={SourceRef}
value={formValues.Source === '' ? {label: ''} : {label: formValues.Source}}
options={SourceStatus}
onChange={useCallback((e, v) => {
if (typeof v === 'object' && v !== null) {
handleInputChange(e, v) // help to set the value
} else {
handleInputChange(e, {label: ''}) // help to reset the value
}
})}
/>
In my case for multiselect freeSolo onChange props 3rd argument reason solved my all issues.
AutocompleteChangeReason can be:
blur
clear
createOption
removeOption
selectOption
and 2nd arg of this props gives u already updated list of (multiselect) value/s.
onChange={(_event, newOptions, reason) => {
setOptions(
reason === 'clear' ? [] : [...newOptions.map((o) => Number(o))],
);
}}
If you need only the selected value, set the value to an empty object and render the option to your needs.
<Autocomplete
value={{}}
onChange={handleSelectionChanged}
options={options ?? []}
getOptionLabel={x => (!x ? '' : x?.name ?? '')}
renderInput={params => <TextField {...params} label="" />}
/>
If you are using objects, you can use the following code to clear the field.
Ensure you add isOptionEqualToValue:
<Autocomplete
style={{ width: 250 }}
multiple
id="checkboxes-tags-demo"
options={list}
isOptionEqualToValue={(option, newValue) => {
return option.id === newValue.id;
}}
value={selected}
onChange={(e, val) => handleSelected(e, val)}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Add to Multiple"
placeholder="Favorites" />
)} />
Just set an empty array in your state through functions, and it'll be cleared.
Try this method:
use onChange method and pass third parameter reason and compare to clear text if reason is clear then executed this function.
<Autocomplete
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
console.log("Put your clear logic here: this condition executed when clear button clicked")
setValue({ title: '', year: '' }) //for reset the value
return
}
}}
/>
One easy way to do this is to pass these props to autocomplete like this:
onChange={handleSkillChange}
inputValue=''
clearOnBlur={true}
onChange is an event handler, which stores the value in the state.
inputValue='' helps to ensure that the text field inside autocomplete will always be empty
clearOnBlur={true} helps to clear the value of the autocomplete component when it loses focus.

Resources