Material-UI Autocomplete onChange not updates value - reactjs

I want to use onChange event on Autocomplete component to get current selected values.
The problem is that it does not working as expected, so when I click to check/uncheck value checkbox is still unchecked but in console i can see that new value was added
uncoment this part to make it works:
value={myTempVal}
onChange={(event, newValue) => {
setMyTempVal(newValue);
console.log(newValue);
}}
online demo:
https://codesandbox.io/embed/hardcore-snowflake-7chnc?fontsize=14&hidenavigation=1&theme=dark
code:
const [myTempVal, setMyTempVal] = React.useState([]);
<Autocomplete
open
multiple
value={myTempVal}
onChange={(event, newValue) => {
setMyTempVal(newValue);
console.log(newValue);
}}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>

You need to get donors receivers and options variables out of the function. Those variables get re-created at each render, this means that their reference changes at each render, and as Autocomplete makes a reference equality check to decide if an option is selected he never finds the options selected.
const donors = [...new Set(data.map(row => row.donor))].map(row => {
return {
groupName: "Donors",
type: "donor",
title: row || "null"
};
});
const receivers = [...new Set(data.map(row => row.receiver))].map(row => {
return {
groupName: "Receivers",
type: "receiver",
title: row || "null"
};
});
const option2 = [...donors, ...receivers];
export const App = props => {
const [myTempVal, setMyTempVal] = React.useState([]);
return (
<Autocomplete
open
multiple
...
You can also add getOptionSelected to overwrite the reference check :
<Autocomplete
open
multiple
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
getOptionSelected={(option, value) => option.title === value.title}
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>

This can help:
Replace
checked={selected}
To
checked={myTempVal.filter(obj=>obj.title===option.title).length!==0}
The complete solution
import React from "react";
import "./styles.css";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import CheckBoxOutlineBlankIcon from "#material-ui/icons/CheckBoxOutlineBlank";
import CheckBoxIcon from "#material-ui/icons/CheckBox";
import Checkbox from "#material-ui/core/Checkbox";
import SearchIcon from "#material-ui/icons/Search";
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const data = [
{ donor: "Trader Joe's", receiver: "Person-to-Person" },
{ donor: "Trader Joe's", receiver: "Homes with Hope" },
{ donor: "Santa Maria", receiver: "Gillespie Center" },
{ donor: "Santa Maria", receiver: null }
];
export const App = props => {
const donors = [...new Set(data.map(row => row.donor))].map(row => {
return {
groupName: "Donors",
type: "donor",
title: row || "null"
};
});
const receivers = [...new Set(data.map(row => row.receiver))].map(row => {
return {
groupName: "Receivers",
type: "receiver",
title: row || "null"
};
});
const option2 = [...donors, ...receivers];
const [myTempVal, setMyTempVal] = React.useState([]);
return (
<Autocomplete
open
multiple
value={myTempVal}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option, { selected }) => {
return (
<>
<Checkbox
onClick={
()=>{
if(myTempVal.filter(obj=>obj.title===option.title).length!==0){
setMyTempVal([...myTempVal.filter(obj=>obj.title!==option.title)],console.log(myTempVal))
}else{
setMyTempVal([...myTempVal.filter(obj=>obj.title!==option.title),option],console.log(myTempVal))
}
}
}
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={myTempVal.filter(obj=>obj.title===option.title).length!==0}
/>
{option.title}
</>
);
}}
options={option2}
// groupBy={option => option.groupName}
getOptionLabel={option => option.title}
renderInput={params => (
<div>
<div>
<SearchIcon />
</div>
<TextField
variant="outlined"
fullWidth
ref={params.InputProps.ref}
inputProps={params.inputProps}
/>
</div>
)}
/>
);
};
export default App;

It is bit late to Answer this question but it might help someone.
In your code you have added onChange event in Autocomplete. When you click on checkbox it will trigger 2 times, one for checkbox and one for Autocomplte. Hence 2nd time trigger makes again checkbox unchecked so u get value in console but still checkbox is empty.
You can remove your checkbox in renderOption and use checked and uncheked icon instaed of checkbox.
renderOption={(option, { selected }) => {
return (
<React.Fragment>
{selected ? <CheckedIcon> : <uncheckedIcon>}
<div>
{option.title}
</div>
</React.Fragment>
</>
);
}}

Related

React MUI Autocomplete with Formik and custom render options

I am trying to implement React Material UI Autocomplete component by using Formik (setFormValue) and custom renderOption property, so I don't use TextField component, but custom input field.
Filtering options works as expected, so when I enter some value in custom input, the results are displayed depending on what I typed in, but I have an issue with selecting an option. When I click when I click one of the offered ones, nothing happens.
I created FilterDropdown component so it can be reusable. Please see my code and tell me where I wrong.
I did debugging through Google chrome breakpoints, and when I click on any available dropdown option, nothing happens.
filter-dropdown/index.tsx
import { createElement } from 'react'
import { Divider } from '#eframe-ui/react'
import { FilterDropdownProps, DropdownOptionProps } from './types'
import { Translate } from '../translate'
import { Autocomplete } from '#mui/material'
import { FilterDropdownInput } from './Input'
export const FilterDropdown = ({
data,
dropdownDataLayout,
selectFieldLabel,
fieldValue,
errors,
setFieldValue,
}: FilterDropdownProps) => {
return (
<Autocomplete
freeSolo
className="gap-y-1 flex flex-col"
options={data}
getOptionLabel={(option: string | DropdownOptionProps) =>
(option as { value: string }).value
} // filter value
onChange={(e, value) => setFieldValue(fieldValue, value)}
renderInput={(params) => (
<FilterDropdownInput
error={errors}
params={params}
label={selectFieldLabel}
/>
)}
renderOption={(props, option: DropdownOptionProps) => {
const { label, mixin } = option
return dropdownDataLayout
? createElement(dropdownDataLayout, mixin as Record<string, string>)
: label && (
<div className="flex flex-col w-full">
<Translate label={label} />
<Divider className="!my-2" />
</div>
)
}}
/>
)
}
Input.tsx
import { TextField } from '#/../ui'
import { useTranslation } from 'react-i18next'
import { AutocompleteRenderInputParams } from '#mui/material'
interface FilterDropdownProps {
params: AutocompleteRenderInputParams
label: string
error: string | JSX.Element | undefined
}
export const FilterDropdownInput = ({
params,
label,
error,
}: FilterDropdownProps) => {
const { t } = useTranslation()
return (
<div ref={params.InputProps.ref}>
<TextField
{...params.inputProps}
label={t(label)}
placeholder={t('common:Field.Search', 'Enter search term')}
error={!!error}
helperText={error}
/>
</div>
)
}
transfer-assets.tsx
const TransferAssetsLayout = (option: Record<string, string>) => (
<div
className="funds flex flex-col w-full cursor-pointer"
onClick={() => setFieldValue('pension_fund_name', option.name)}
>
<p className="text-text-primary typo-regular-300 px-2">{option.name}</p>
{option.recipient && (
<p
className={clsx(
classes.italic,
'text-text-secondary typo-regular-200 px-2 pb-1',
)}
>
{option.recipient}
</p>
)}
<p
className={clsx(
classes.italic,
'text-text-secondary typo-regular-200 px-2 pb-1',
)}
>
{`${option.address}, ${option.zip_code} ${option.location}`}
</p>
<Divider className="!m-0" />
</div>
)
<FilterDropdown
data={pensionFundsData}
dropdownDataLayout={TransferAssetsLayout}
selectFieldLabel="USM-PortfolioEdit:TransferAssets.InstitutionName"
fieldValue={'pension_fund_name'}
buttonLabel={pension_fund_name}
setFieldValue={() => setFieldValue}
errors={errors.pension_fund_name}
/>
You need to apply the MUI input props or Autocomplete loses track of your custom input.
Instead of this:
renderInput={(params) => (
<FilterDropdownInput
error={errors}
params={params}
label={selectFieldLabel}
/>
)}
Use this:
renderInput={(params) => (
<FilterDropdownInput
{...params}
error={errors}
params={params}
label={selectFieldLabel}
/>
)}
Example working sandbox of MUI Autocomplete passing props down to through renderInput. You must use {...params} exactly as can be verified.
<Autocomplete
options={someData.sort((a, b) => a.value - b.value)}
getOptionLabel={(option) => option.label}
value={value}
onChange={handleChange}
disabled={(someData || []).length >= 5}
renderInput={(params) => (
<TextField
{...params}
size="small"
label="Type some stuff"
variant="outlined"
/>
)}
/>
I fixed the issue , this is my solution
import { createElement, HTMLAttributes } from 'react'
import { Divider } from '#eframe-ui/react'
import { FilterDropdownProps, DropdownOptionProps } from './types'
import { Translate } from '../translate'
import { Autocomplete, Paper } from '#mui/material'
import { FilterDropdownInput } from './Input'
export const FilterDropdown = ({
data,
dropdownDataLayout,
selectFieldLabel,
fieldValue,
errors,
setFieldValue,
}: FilterDropdownProps) => {
/* eslint-disable #typescript-eslint/no-explicit-any */
const CustomPaper = (props: any) => (
<Paper {...props} sx={{ background: '#f4f7fa', marginTop: '4px' }} />
)
return (
<Autocomplete
freeSolo
className="gap-y-1 flex flex-col"
options={data}
getOptionLabel={(option: string | DropdownOptionProps) =>
(option as { value: string }).value
} // filter value
onChange={(e, value) => setFieldValue(fieldValue, value)}
renderInput={(params) => (
<FilterDropdownInput
{...params}
error={errors}
params={params}
label={selectFieldLabel}
/>
)}
renderOption={(
props: HTMLAttributes<HTMLLIElement>,
option: DropdownOptionProps,
) => {
const { label, mixin } = option
return (
<>
<li {...props} className="w-full cursor-pointer">
{dropdownDataLayout
? createElement(
dropdownDataLayout,
mixin as Record<string, string>,
)
: label && (
<>
<Translate label={label} />
<Divider className="!my-2" />
</>
)}
</li>
</>
)
}}
PaperComponent={CustomPaper}
/>
)
}

value returns 0 or undefined on MUI autocomplete unable to fetch the value in multiple attribute

On MUI Autocomplete component where the attribute is multiple, then the value prop returns 0 or undefined when we choose anyone of the selected option.
<Autocomplete
value={v.education}
onChange={handleEducationChange}
className={classes.textOutline}
multiple
id="virtualize-demo"
name="education"
style={{ width: 600 }}
disableCloseOnSelect
ListboxComponent={ListboxComponent}
options={educationList()}
getOptionLabel={(option) => option.title}
isOptionEqualToValue={(option, value) =>
option.title === value.title
}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</li>
)}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
name="education"
placeholder="Select your Qualification"
label="Education"
fullWidth
/>
)}
/>
// handleEducationChange
const initialState = { education: "", occupation: "" };
const [selectedOption, setSelectedOption] = useState([], initialState);
const handleEducationChange = (event) => {
const { name, values } = event.target;
setSelectedOption({ ...selectedOption, [name]: values });
console.log(values);
};
I need the selected value to be passed with value prop and that can be validated and posted to backend... kindly update the solution for this, Thanks.

How to get an option after choosing one | Material UI

I have the Material UI Country select Autocomplete, but the chosen option is not showing to the frontend, already configured a code to save the option to the server, but failed to show it on the frontend.
CountrySelect.jsx:
export default function CountrySelect({ user, country, setCountry, defaultValue, getOptionLabel }) {
// onChange={(e) => setCountry(e.target.value)}
return (
<Autocomplete
id="country-select-demo"
sx={{ maxWidth: true, background: '#f3f3f4' }}
options={countries}
defaultValue={{ label: `${user.country}` }}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props}>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
alt=""
/>
{option.label} ({option.code}) +{option.phone}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
inputProps={{
...params.inputProps,
autoComplete: 'new-password', // disable autocomplete and autofill
}}
defaultValue=""
onBlur={(e) => setCountry(e.target.value)}
/>
)}
/>
);
}
const countries = [
// COUNTRIES
];
EditProfile.jsx:
import { useContext, useState } from "react"
import { Context } from "../../context/Context";
import CountrySelect from "../../components/countrySelect/CountrySelect";
export default function Settings() {
const [country, setCountry] = useState("");
const { user, dispatch, isFetching } = useContext(Context);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({type: "UPDATE_START"})
const updatedUser = {
userId: user._id,
firstName,
lastName,
birthday,
country,
};
try {
const res = await axios.put("/users/"+user._id, updatedUser);
setSuccess(true);
dispatch({ type: "UPDATE_SUCCESS", payload: res.data });
} catch (err) {
dispatch({type: "UPDATE_FAILURE"});
}
};
return(
<form className="settingsForm" onSubmit={handleSubmit}>
<CountrySelect
country={country}
setCountry={setCountry}
defaultValue={user.country} />
</form>
)
}
The code is working fine, I get the user input option saved in the server (Mongodb), but I want to show the option to the frontend like a placeholder or default value! just want to show the chosen option, Now it's only showing Choose a country

Material UI: How do I reset inputValue when using InputBase for Autocomplete?

The following will allow you to prepare your own behavior like the one in the title, but it is not possible to do it in the following way
However, since debounce() is called each time onInputChange is performed, if the key is pressed for a long time
This method is not realistic because the processing becomes heavy and the input value becomes choppy.
How can the inputBase value be reset in such a case?
const [q, setQ] = useState('');
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement>,
value: string
) => {
setQ(value);
debounce(value);
};
const handleClickClear = () => {
setQ('');
debounce('');
};
<Autocomplete
getOptionLabel={(option) =>
typeof option === "string" ? option : option.word
}
inputValue={q}
options={data}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<InputBase
inputProps={{
...params.inputProps,
name: "search",
type: "text",
}}
/>
{q && (
<ButtonBase onClick={handleClickClear}>
<IconButton size="small">
<ClearIcon fontSize="small" />
</IconButton>
</ButtonBase>
)}
</div>
)}
blurOnSelect
freeSolo
openOnFocus
onChange={handleChange}
onFocus={handleFocus}
onInputChange={handleInputChange}
/>;

How to get mui Chip value before delete on Autocomplete freeSolo?

I'm working with Autocomplete and Chip component from the mui library. There's the DEMO (standard boilerplate).
I can't get the Chip contents before deleting it:
<Autocomplete
multiple
id="tags-filled"
options={top100Films.map((option) => option.title)}
defaultValue={[top100Films[1].title]}
freeSolo
onKeyDown={(prop) => {
if (prop.key === 'Enter') {
console.log(prop.target.value)
}
}}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
onDelete={(s) => console.log("the one", option)}
key={index} variant="outlined"
label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="freeSolo"
placeholder="Favorites"
/>
)}
/>
The issue is this
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
onDelete={(s) => console.log("the one", option)}
key={index} variant="outlined"
label={option} {...getTagProps({ index })} />
))
}
If I remove {...getTagProps({ index })} I do get the onDelete working the way I need it to but then the actual removal doesn't work. Again, the DEMO here
you can use
onChange={(e, value, situation, option) => {
if (situation === "removeOption") {
//write your code here
console.log("--->", e, value, situation, option);
}
setReceivers((state) => value);
}}
instead of the onDelete like this :
import * as React from "react";
import Chip from "#mui/material/Chip";
import Autocomplete from "#mui/material/Autocomplete";
import TextField from "#mui/material/TextField";
import Stack from "#mui/material/Stack";
export default function Tags() {
const [val, setVal] = React.useState({});
const [receivers, setReceivers] = React.useState([]);
console.log(receivers);
const handleClick = () => {
setVal(top100Films[0]); //you pass any value from the array of top100Films
// set value in TextField from dropdown list
};
return (
<Stack spacing={1} sx={{ width: 500 }}>
<Autocomplete
multiple
id="tags-filled"
options={top100Films.map((option) => option.title)}
defaultValue={[top100Films[13].title]}
freeSolo
onChange={(e, value, situation, option) => {
if (situation === "removeOption") {
console.log("--->", e, value, situation, option);
}
setReceivers((state) => value);
}}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="freeSolo"
placeholder="Favorites"
/>
)}
/>
</Stack>
);
}
codesandbox

Resources