Autocomplete hover not responding properly - reactjs

I have created an Autocomplete component that rely on the selected value of the parent Autocomplete.
This working perfectly. When I type or hover in order to select then, both are working as expected.
const [hoverd, setHoverd] = useState(false);
const onMouseOver = () => setHoverd(true);
const onMouseOut = () => setHoverd(false);
<div onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
<Card className={classes.paper} raised={hoverd}>
<Grid item>
<Autocomplete
id="filter-by"
className={classes.search}
options={filterBy}
value={filterByValue}
onChange={(event, newValue) => {
setFilterByValue(newValue)
}}
renderInput={(params) =>
<TextField {...params}
variant="outlined"
label="Search by: ?"
InputProps={{
...params.InputProps,
}}
/>}
/>
</Grid>
{filterByValue === 'Campaign' ? <Grid item>
<Autocomplete
id="filter-by-campaign"
className={classes.search}
options={campaignData}
getOptionLabel={(option) => option.campaign}
renderInput={(params) =>
<TextField {...params}
variant="outlined"
label="Search by: campaign"
/>}
/>
</Grid> : <div />}
When I am moving the code to a separate component then its not working. I can type but the hover option not working.
{filterByValue === 'Campaign' ? <FilterByCampaign /> : <div />}
const FilterByCampaign = () => {
return <Grid item>
<Autocomplete
id="filter-by-campaign"
className={classes.search}
options={campaignData}
getOptionLabel={(option) => option.campaign}
renderInput={(params) =>
<TextField {...params}
variant="outlined"
label="Search by: campaign"
/>}
/>
</Grid>
}
I don’t understand what is the different between this two.
Any idea ?
Thank you

Try passing your onMouseOver function to the child component as a prop and then to the input
{filterByValue === 'Campaign' ? <FilterByCampaign onMouseOver={onMouseOver} /> : <div />}
const FilterByCampaign = ({ onMouseOver }) => {
return <Grid item>
<Autocomplete
id="filter-by-campaign"
className={classes.search}
options={campaignData}
getOptionLabel={(option) => option.campaign}
onMouseOver={onMouseOver} // depending on what the onMouseOver does either here
renderInput={(params) =>
<TextField
{...params}
onMouseOver={onMouseOver} // or here
variant="outlined"
label="Search by: campaign"
/>}
/>
</Grid>
}

Related

Can we remove the calendar icon from the TextField (MUI)?

Here is my basic code to accomplish the task, but couldn't come up with any outcome
<TextField
sx={{
'&::-webkit-calendar-picker-indicator': {
display: 'none',
'-webkit-appearance': 'none',
},
}}
id="outlined-basic"
variant="outlined"
type="date"
helperText="Please select the date"
/>
Also, I did a bit research on InputProps (endAdornment) within TextField to remove the icon, but that didn't work.
You can define in the components property the icon to be null for both cases.
<TimePicker
label="Time"
value={value}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
disableOpenPicker
/>
<DateTimePicker
label="Date&Time picker"
value={value}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
disableOpenPicker
/>
Here is a working sandbox
I guess u r using MUIv5
(and I guess u talking about DatePicker)
In that case (as u mentioned), the icon is rendered cuz the InputProps with endAdornment is passed to the text field, if u omit that prop, there will be no icon.
<DatePicker
label="Basic example"
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={({ InputProps, ...props }) => (
<TextField {...props} />
)}
/>

Create tag using Autocomplete material UI when user input anything

I can type something which shows selected tags from the dropdown list but I want a user can type something and create a tag or multiple tags separated by a comma.
I used a useState hook which is an array.
const [tags, setTags] = useState([]);
I set the Autocomplete like the following code -
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
onChange={(e) => setTags([...tags, e.target.value.split(",")])}
/>
)}
/>;
Surprisingly, I tried for an hour before questioning. But Solve it within a few moments after putting the question.
Here's the solution-
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[...tags]}
freeSolo
autoSelect
onChange={(e) => setTags([...tags, e.target.value])}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
/>
)}
/>;
the output will look like this.
user input tag
but I want to add multiple tags that didn't happen right now which can be put via giving a space or comma.
Partly relevant but I think it can be helpful for someone using React Hook Form library with MUI5.
Storing tags in the state would render them being saved in form data when submitted. Instead, you need to use their onChange function.
const Tags = ()=>{
const { control,handleSubmit } = useForm({
defaultValues: {
checkbox: true,
autocomplete:["test"]
}
});
const onSubmit = data => console.log(data);
return <>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="autocomplete"
control={control}
render={({ field }) => {
const {value,onChange} = field
return <Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={value}
defaultValue={[...value]}
freeSolo
autoSelect
onChange={((e)=>onChange([...value,e.target.value]))}
renderInput={(params) => {
return <TextField
{...params}
label="Tags"
placeholder="Tags"
value={value}
/>
}}
></Autocomplete>
}
}
/>
<input type="submit" />
</form>
</>
}

Dynamically adding or removing items within a react-hook-forms form and registering the inputs?

I'm trying to implement a rather complicated form that has and date picker and input which the user can add multiple of (or delete). That data then gets added to the overall form on submit. How do I get react-hook-forms to register that little dynamic faux form within the real form?
Here's the faux form inputs:
<AddPackagesStyle>
<Label htmlFor="addedPackages" label="Add Packages" />
<DatePicker
id="dateRange"
selected={startDate}
selectsRange
startDate={startDate}
endDate={endDate}
placeholderText="select dates"
onChange={onDateChange}
/>
<PackageInput
id="PackageSelect"
placeholder="Select Package"
type="text"
value={name}
// #ts-ignore
onChange={(e) =>
// #ts-ignore
setName(e.target.value)
}
/>
<ButtonContainer>
<button type="button" onClick={clearAll}>
Clear
</button>
<button
type="button"
// #ts-ignore
onClick={addPackage}
>
Add
</button>
</ButtonContainer>
</AddPackagesStyle>
These entries get added to an array in a useState hook:
const [addedPackages, setAddedPackages] = useState<any[]>([])
Then this gets rendered in JSX as the add packages:
<ContentSubscriptionWrapper>
{addedPackages.length !== 0 &&
addedPackages.map((addedPackage, idx) => (
// #ts-ignore
<>
<ContentSubscriptionColumn>
{addedPackage.name && addedPackage.name}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.startDate &&
addedPackage.startDate.toString()}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.endDate && addedPackage.endDate.toString()}
</ContentSubscriptionColumn>
<button type="button" onClick={() => removePackage(idx)}>
X
</button>
</>
))}
</ContentSubscriptionWrapper>
So before the form gets submitted, the 'add packages' has to be set. Where do I add the {...register} object to add to the larger form object for submission?
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<any>()
const onSubmit = (data: any) => {
console.log(data)
}
I created a CodeSandbox trying to reproduce your use case and used Material UI to get it done quickly, but you should get the idea and can modify it with your own components.
you should let RHF handle all the state of your form
use RHF's useFieldArray for managing (add, remove) your packages/subscriptions - there is no need to use watch here
use a separate useForm for your <AddPackage /> component, this has the benefit that you will have form validation for this sub form (in case it should be a requirement that all fields of <AddPackage /> need to be required) - i added validation in the demo to demonstrate this
AddPackage.tsx
export const AddSubscription: React.FC<AddSubscriptionProps> = ({ onAdd }) => {
const {
control,
reset,
handleSubmit,
formState: { errors }
} = useForm<Subscription>({
defaultValues: { from: null, to: null, package: null }
});
const clear = () => reset();
const add = handleSubmit((subscription: Subscription) => {
onAdd(subscription);
clear();
});
return (
<Card variant="outlined">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Grid container spacing={1} p={2}>
<Grid item container spacing={1} xs={12}>
<Grid item xs={6}>
<Controller
name="from"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="From"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.from}
helperText={errors.from?.message}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
name="to"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="To"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.to}
helperText={errors.to?.message}
/>
)}
/>
)}
/>
</Grid>
</Grid>
<Grid item xs={12}>
<Controller
name="package"
control={control}
rules={{ required: "Required" }}
render={({ field: { onChange, ...field } }) => (
<Autocomplete
{...field}
options={packages}
onChange={(e, v) => onChange(v)}
renderInput={(params) => (
<TextField
{...params}
label="Package"
fullWidth
error={!!errors.package}
helperText={errors.package && "Required"}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Stack spacing={1} direction="row" justifyContent="end">
<Button variant="outlined" onClick={clear}>
Clear
</Button>
<Button variant="contained" onClick={add} type="submit">
Add
</Button>
</Stack>
</Grid>
</Grid>
</LocalizationProvider>
</Card>
);
};
Form.tsx
export default function Form() {
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: {
seats: "",
addOns: false
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "subscriptions"
});
const onSubmit = (data) => console.log("data", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box display="flex" justifyContent="end" gap={1}>
<Button variant="outlined">Cancel</Button>
<Button variant="contained" type="submit">
Save
</Button>
</Box>
</Grid>
<Grid item xs={12}>
<Controller
name="seats"
control={control}
render={({ field }) => (
<TextField {...field} fullWidth label="Seats" />
)}
/>
</Grid>
<Grid item xs={12}>
<AddSubscription onAdd={append} />
</Grid>
<Grid item xs={12}>
<List>
{fields.map((field, index) => (
<ListItem
key={field.id}
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
onClick={() => remove(index)}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemText
primary={field.package.label}
secondary={
<span>
{formatDate(field.from)} - {formatDate(field.to)}
</span>
}
/>
</ListItem>
))}
</List>
</Grid>
<Grid item xs={12}>
<Controller
name="addOns"
control={control}
render={({ field: { value, onChange } }) => (
<FormControlLabel
control={<Checkbox checked={!!value} onChange={onChange} />}
label="Add-ons"
/>
)}
/>
</Grid>
</Grid>
</form>
);
}

React Material UI + Formik FieldArray Autocomplete control value stays same on remove

I want use Formik's form validation and it actually works just fine, but I ran into some issues with selected value display in Autocomplete component. I Have created Add/Remove buttons to dynamically adjust how many rows are in my form. The bug occurs when I try to remove form row, the row below, behind scenes has proper values as displayed in DEBUG, but user's input displays value from deleted form row. I cannot figure out, how to display or handle this occurrence.
Form before removal,
Form after removal
<FieldArray name="actions"
render={arrayHelpers =>
values.actions.map((action, index) => (
<Grid item container spacing={1} justify="center" alignItems="center"
key={index}>
<Grid item xs={4}>
<Field
error={getIn(errors, `actions.${index}.suggestedAction`) &&
getIn(touched, `actions.${index}.suggestedAction`)}
helperText={<ErrorMessage
name={`actions.${index}.suggestedAction`}/>}
name={`actions.${index}.suggestedAction`}
id={`actions.${index}.suggestedAction`}
variant="outlined"
fullWidth
as={TextField}
label="Siūloma priemonė"
multiline
rows={3}
rowsMax={10}
/>
</Grid>
<Grid item xs={4}>
<Autocomplete
freeSolo
onBlur={handleBlur}
onChange={(e, value) => {
//If adding new
if (value && value.inputValue) {
setOpen(true);
setFieldValue(`actions.${index}.responsiblePerson`, value.inputValue)
} else if (value && value.id) {
//Selecting existing
setFieldValue(`actions.${index}.responsiblePerson`, value.id)
} else {
setFieldValue(`actions.${index}.responsiblePerson`, "")
}
}}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option;
}
if (option.inputValue) {
return option.inputValue;
}
return option.title;
}}
handleHomeEndKeys
clearText="Pašalinti"
noOptionsText="Tuščia..."
renderOption={option => option.title}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
title: `Pridėti "${params.inputValue}"`,
});
}
return filtered;
}}
renderInput={params => (
<TextField
{...params}
id={`actions.${index}.responsiblePerson`}
name={`actions.${index}.responsiblePerson`}
error={getIn(errors, `actions.${index}.responsiblePerson`) &&
getIn(touched, `actions.${index}.responsiblePerson`)}
helperText={<ErrorMessage
name={`actions.${index}.responsiblePerson`}/>}
onChange={handleChange}
variant="outlined"
label="Atsakingas asmuo"
placeholder="Vardenis Pavardenis"
/>
)}
options={top100Films}/>
</Grid>
<DateTimeUtilsProvider>
<Grid item xs={3}>
<Field
disableToolbar
as={KeyboardDatePicker}
variant="inline"
inputVariant="outlined"
format="yyyy-MM-dd"
id={`actions.${index}.deadline`}
name={`actions.${index}.deadline`}
error={getIn(errors, `actions.${index}.deadline`) &&
getIn(touched, `actions.${index}.deadline`)}
helperText={<ErrorMessage
name={`actions.${index}.deadline`}/>}
label="Įvykdymo terminas"
onChange={value =>
setFieldValue(`actions.${index}.deadline`, value)}
/>
</Grid>
</DateTimeUtilsProvider>
<Grid item xs={1}>
<ButtonGroup fullWidth orientation="vertical" size="medium">
<Button onClick={() => {
arrayHelpers.remove(index);
}}
disabled={values.actions.length === 1}
classes={removeButtonClasses}>
<HighlightOff/>
</Button>
<Button onClick={() => {
arrayHelpers.insert(index + 1, {
suggestedAction: "",
responsiblePerson: "",
deadline: Date.now()
})
}}
color="primary">
<AddCircleOutline/>
</Button>
</ButtonGroup>
</Grid>
</Grid>
))
}
/>
</Grid>
Instead of
arrayHelpers.insert()
I have used
arrayHelpers.push()
and its working fine for me.
I had this same problem. I was setting a value prop on the <Field> inside my renderInput.
<Autocomplete
renderInput={params => (
<Field {...params} component={TextField} value={values.myArray[index]} />
)}
/>
I was able to fix it by moving the value attribute to the Autocomplete.
<Autocomplete
value={values.myArray[index]}
renderInput={params => (
<Field {...params} component={TextField} />
)}
...
/>
This worked for me
const arrayValue = options.filter(
(item) => item.id === values[arrayName][index][name]);
And then I used the filtered option as my value in the Autocomplete component
<Autocomplete
name={name}
value={arrayValue.length > 0 ? arrayValue[0] : null}
options={options}
groupBy={(option) => option.group}
getOptionLabel={(option) => option.value}
isOptionEqualToValue={(option, value) => option?.id === value?.id}
defaultValue={defaultValueCheck()}
onChange={(_, value) => {
setFieldValue(`${arrayName}[${index}].${name}`, value?.id ?? "");
}}
renderInput={(params) => (
<TextField
{...params}
{...configTextField}
name={`${arrayName}[${index}].${name}`}
/>
)}
renderGroup={(params) => (
<li key={params.key}>
<GroupHeader>{params.group}</GroupHeader>
<GroupItems>{params.children}</GroupItems>
</li>
)}
/>
</>

MUI Autocomplete's 'defaultValue' not working when used with Controller of react-hook-form

I am trying to use MUI's Autocomplete with react-hook-form. I have wrapped an Autocomplete component in React Hook Form's Controller. When I try to set defaultValue to AutoComplete it does not work, when I try to change the preset value the Autocomplete component breaks.
Here is the snippet from my code.
<Controller
name="combo-box-demo"
control={control}
defaultValue={top100Films.find(film => film.year === selectedFilmYear)}
as={
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionLabel={option => option.title}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
}
/>
Sandbox link of working demo code is here.
You should add an onChange prop on the Controller and return the selected object value
Also you can then implement getOptionSelected AutoComplete
export default function ComboBox() {
const { control } = useForm({});
const [selectedFilmYear, setSelectedFilmYear] = React.useState(1994);
return (
<Controller
name="combo-box-demo"
control={control}
defaultValue={top100Films.find(film => film.year === selectedFilmYear)}
onChange={([val, obj]) => obj}
as={
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionSelected={(obj, newval) => obj.name === newval.name}
getOptionLabel={option => option.title}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
}
/>
);
}

Resources