Material UI autocomplete with redux-form values getting destroyed - javascript

I'm trying to use Material UI's autocomplete component with redux wizard form
I was able to link the autocomplete provided by material UI but when i go back to previous page and come back to the second page where the autocomplete fields are, the field gets destroyed despite having destroyOnUnmount set to false. (All other fields doesn't get destroyed but only the fields in page 2 which uses material UI autocomplete feature) Actually I dont think it's getting destroyed because the value is still there you just can't see it in the input
Also When I click Submit, the Main Hobby section's value gets through but the multiple hobby section's value doesnt get through. Can anyone have a look and see what's wrong? Thanks

You need to define the value attribute of AutoComplete to show the selected values when you visit the form again.
You must note that the two fields in Hobby form need to be defined with different name
Also the onChange value of multi select AutoComplete need to inform reduxForm about the change
MultipleComplete.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
const hobbies = [
{ title: "WATCHING MOVIE" },
{ title: "SPORTS" },
{ title: "MUSIC" },
{ title: "DRAWING" }
];
const MultipleComplete = ({
input,
meta: { touched, error, submitFailed }
}) => {
const { onChange, ...rest } = input;
return (
<div>
<Autocomplete
multiple
limitTags={2}
value={input.value || []}
id="multiple-limit-tags"
options={hobbies}
onChange={(e, newValue) => {
onChange(newValue);
}}
getOptionLabel={option => option.title}
getOptionSelected={(option, value) => option.title === value.title}
renderInput={params => (
<TextField
{...params}
variant="outlined"
placeholder="Choose Multiple Hobbies"
fullWidth
/>
)}
/>
</div>
);
};
export default MultipleComplete;
AutoHobbyComplete.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
const hobbies = [
{ title: "WATCHING MOVIE" },
{ title: "SPORTS" },
{ title: "MUSIC" },
{ title: "DRAWING" }
];
const AutoHobbyComplete = ({
input,
meta: { touched, error, submitFailed }
}) => {
const getSelectedOption = () => {
return hobbies.find(o => o.title === input.value);
};
const { onChange, ...rest } = input;
return (
<div>
<Autocomplete
autoSelect
value={getSelectedOption()}
options={hobbies}
autoHighlight
getOptionLabel={option => option.title}
onChange={(event, newValue) => onChange(newValue)}
getOptionSelected={(option, value) => {
return option.title === value.title || option.title === input.value;
}}
renderInput={params => {
return (
<TextField
{...params}
{...rest}
value={input.value}
variant="outlined"
fullWidth
/>
);
}}
/>
</div>
);
};
export default AutoHobbyComplete;

It seems that youre correct, the value just isnt being displayed properly. If you are able to get the value from your redux form you can pass that value as an inputValue to the autocomplete. This will display the value in the text box. Make sure to use inputValue and not value.
<Autocomplete
inputValue={//this is where your redux form value should be displayed}
autoSelect
options={hobbies}
autoHighlight
getOptionLabel={option => option.title}
onChange={(event, newValue) => console.log(newValue)}
getOptionSelected={(option, value) => option.title === value.title}
renderInput={params => (
<TextField {...params} {...input} value="test" variant="outlined" fullWidth />
)}
/>

Related

How can I prevent mUI from showing dropdown menu after Chrome autofills a user's address?

I'm creating a form for a user to input their address for the purposes of collecting a billing address for payment information. We only serve customers within the United States so there is an autocomplete mUI component with a dropdown menu so that the user can select which state they live in. Here is the code we are using for that:
export const stateNamesAndAbbreviations: ReadonlyArray<StateNameAbbreviation> = [
{ name: "Alabama", abbreviation: "AL" },
{ name: "Alaska", abbreviation: "AK" },
{ name: "Arizona", abbreviation: "AZ" },
// Shortened for brevity. . .
]
const StateDropdown: React.FC<StateDropDownProps> = ({state, fieldUpdated}) =>
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete size="small"
autoSelect
autoHighlight
selectOnFocus
options={stateNamesAndAbbreviations.map((x) => x.abbreviation)}
data-testid={NewPaymentMethodTestHandles.State}
onChange={(event, newValue) => fieldUpdated(PaymentMethodFields.state, newValue!)}
renderInput={(params) => (
<TextField variant="outlined"
{...params}
error={state.isDirty && !state.isValid}
helperText={state.isDirty && !state.isValid ? "State is required" : null}
data-testid={NewPaymentMethodTestHandles.StateInput}
onChange={(event) => fieldUpdated(PaymentMethodFields.state, event.currentTarget.value)}
onBlur={() => !state.isDirty && fieldUpdated(PaymentMethodFields.state, state.value)}
inputProps={{
...params.inputProps,
autoComplete: "address-level1",
}}
/>
)}
/>
</Stack>
Edit: A code sandbox is available here. It's stripped down but has the same problem: if you select the "Name" box and autofill an address saved in your browser the state dropdown menu opens rather than being auto filled.
The dropdown works fine in tandem with the rest of the form which we use to collect first name, last name etc. The issue is when it comes to the browser autofilling a saved address. All of the fields will be populated properly except the state dropdown component, which for whatever reason simply opens the dropdown menu instead of being populated with the state name. I'm still able to properly submit the form by manually selecting a state, and even have the browser save the address I inputted, but autofill does not seem to work on this field. I verified the state is indeed attempting to go to the correct field, since if I remove the dropdown functionality but keep the textfield intact the state gets autofilled correctly. Additionally, when Chrome brings up the menu to autofill the information hovering on it to to preview the info it is about to autofill, the state will be previewed in the correct place.
Any help is much appreciated.
Ok so i'm not an expert in react nor am I an expert in mUI, so pardon me if my code is not according to those libs' best practices. The way I see it, I think you just need to add a handler to the autocomplete's <TextField> when there is a value change (whether it's from autofills or it's from user input).
Before showing my approach, I actually don't know how you manage the states for the inputs, so I will just add the states according to what I think are needed. So my approach is actually just to bind the value of the autocomplete to a state with value={value}, then in the <TextField>, I add onChange listener which listens to input change (normally it's only triggered by user change, but at least in my browser, it's triggered by autofill change as well). Inside the listener, it will try to check if the inputted string exists in the abbreviation mapping stateNamesAndAbbreviations, if it does exist, it immediately update the state value to the the abbreviation of the inputted string, if it doesn't exist, it will not do anything.
Here's the modified codesandbox, and here's the modified dropdown component:
const StateDropdown = ({ state, fieldUpdated }) => {
const [value, setValue] = React.useState(null);
const [open, setOpen] = React.useState(false);
const closeDropdown = () => setOpen(false);
const openDropdown = () => setOpen(true);
const dropdownInput = React.useRef(null);
return (
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete
open={open}
onOpen={openDropdown}
onClose={closeDropdown}
value={value}
onChange={(event, newValue) => {
setValue(newValue || null);
}}
size="small"
autoSelect
autoHighlight
selectOnFocus
options={stateNamesAndAbbreviations.map((x) => x.abbreviation)}
renderInput={(params) => (
<TextField
inputRef={dropdownInput}
onChange={(event) => {
const newValue = event.target.value;
const existingState = stateNamesAndAbbreviations.find(
(state) => state.name === newValue
);
// only force close dropdown when input is updated but is not on focus
if (document.activeElement !== dropdownInput.current) {
if (!!existingState) {
setValue(existingState.abbreviation);
}
closeDropdown();
}
}}
variant="outlined"
{...params}
inputProps={{
...params.inputProps,
autoComplete: "address-level1"
}}
/>
)}
/>
</Stack>
);
};
There is a potential problem that I could think of though. Suppose we have the following mapping:
[
{ name: "Indiana", abbreviation: "IN" },
{ name: "Indianajones", abbreviation: "IJ" },
]
When, for instance, a user is trying to input Indianajones, the dropdown will immediately change the input and select IN instead of allowing the user to continue typing. Despite that, it seems that this wouldn't happen here, since your mapping doesn't have any name which is a prefix of another name, so as long as the mapping is the one you provided, this won't happen.
I solved the potential problem by only update the dropdown value when the dropdown input is not being focused, so whenever the dropdown value is updated by anything than user input, it will be validated.
This is quite simple you just need to see when autocomplete input of AutoComplete
const StateDropdown: React.FC<StateDropDownProps> = ({state, fieldUpdated}) => {
const [value, setValue] = useState('')
const [open, setOpen] = useState(false)
return (
<Stack width={"100%"}>
<InputLabel shrink>State</InputLabel>
<Autocomplete size="small"
autoSelect
autoHighlight
selectOnFocus
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
value={value}
onChange={(event, value) => setValue(value)}
onInputChange={(event, value) => {
if (!stateNamesAndAbbreviations.find(item => item.abbreviation === value)) return
setValue(value)
setOpen(true)
setTimeout(() => setOpen(false)) // because on task end
}}
options={stateNamesAndAbbreviations.map((x) => x.abbreviation)}
data-testid={NewPaymentMethodTestHandles.State}
onChange={(event, newValue) => fieldUpdated(PaymentMethodFields.state, newValue!)}
renderInput={(params) => (
<TextField variant="outlined"
{...params}
error={state.isDirty && !state.isValid}
helperText={state.isDirty && !state.isValid ? "State is required" : null}
data-testid={NewPaymentMethodTestHandles.StateInput}
onChange={(event) => fieldUpdated(PaymentMethodFields.state, event.currentTarget.value)}
onBlur={() => !state.isDirty && fieldUpdated(PaymentMethodFields.state, state.value)}
inputProps={{
...params.inputProps,
autoComplete: "address-level1",
}}
/>
)}
/>
</Stack>
)
}
here is the example i solved your sandbox: https://codesandbox.io/s/practical-kapitsa-ok15r6

Moving from v4 to v5 broke my CleaveJS controlled TextField implementation, and I can't figure out how to fix it

Fiddle: https://codesandbox.io/s/priceless-bohr-x04cjf?file=/src/App.js
const CleaveField:FC<FormFieldProps> = (props) => {
const {onBlur = null, display, inputProps, InputProps, control, name, register, initialValue, errors, info} = props
return (
<Box errors={errors} info={info}>
<Controller
name={name}
control={control}
defaultValue={initialValue ? initialValue : ""}
onBlur={onBlur}
render={({ field }) => {
if (onBlur) {
field = {...field, onBlur}
}
return (
<TextField
{...field}
{...FORM_DEFAULTS}
error={errors && errors.length > 0}
label={display}
inputProps={inputProps}
InputProps={{
inputComponent: CleaveTextField
}}
/>
)
}}
/>
</Box>
)
}
export const CleaveTextField:FC = ({ inputRef, ...otherProps }) => (
<Cleave {...otherProps} inputRef={(ref) => inputRef(ref)} />
)
Above is the code that was stable in v4. The problem is that the TextField improperly initializes the initialValue. It does not trigger the TextField value != zero mechanism, pictured below:
There are some instructions for the migration that I attempted, but so far my attempts have been fruitless. https://mui.com/guides/migration-v4/#heading-textfield
//Change ref forwarding expectations on custom inputComponent. The component should forward the ref prop instead of the inputRef prop.
-function NumberFormatCustom(props) {
- const { inputRef, onChange, ...other } = props;
+const NumberFormatCustom = React.forwardRef(function NumberFormatCustom(
+ props,
+ ref,
+) {
const { onChange, ...other } = props;
return (
<NumberFormat
{...other}
- getInputRef={inputRef}
+ getInputRef={ref}
I have reviewed the git issues and there is nothing outstanding, so either my use case is narrow and there is a bug or I am failing to implement the new API for v5. In either case I have spent way more time than I'd like to admit trying every combination of props to fix this, to no avail.
If I cannot find a solution, my workaround will be to enable the helperText field instead of label, which will break consistency for my form designs.
If anyone has insight here I would be hugely grateful! Thanks.
Current iteration: (Fails identically to the legacy code)
const CleaveField:FC<FormFieldProps> = (props) => {
const {onBlur = null, setValue: setValueForm, display, inputProps, InputProps, control, name, register, initialValue = "", errors, info} = props
const {
field: { onChange, onBlur:onBlurField, name:formName, value, ref },
fieldState: { invalid, isTouched, isDirty },
formState: { touchedFields, dirtyFields }
} = useController({
name,
control,
defaultValue: initialValue,
});
return (
<Box errors={errors} info={info}>
<TextField
{...FORM_DEFAULTS}
value={value}
name={formName}
error={errors && errors.length > 0}
label={display}
onChange={onChange}
onBlur={onBlur || onBlurField}
inputProps={{...inputProps}}
InputProps={{
inputComponent: CleaveTextField,
}}
/>
</Box>
);
}
export const CleaveTextField = React.forwardRef(function CleaveTextField(
props,
ref,
) {
return <Cleave {...props} ref={ref}/>
})

Option from Autocomplete aren't getting removed in React MaterialUI

I am beginner in ReactJS and currently working with Material UI Autocomplete component. I have a data object with keys as table name and columns as their value; an array of objects called columnData(is populated from values of data) with field, ifSelected as its attributes, and field attribute used to set the option label of Autocomplete(which are rendered as Chip). When an option is selected in Autocomplete dropdown then corresponding ifSelected attribute is set to true. However, problem comes when columnData object changes when a user selects different table name from data object and its corresponding value is assigned to columnData, in which previously selected option label isn't getting removed even when ifSelected attribute of all the option is false.
I went through material-ui docs here and tried fixing it using getOptionSelected prop but faced issue with deletion of chips(i.e options) with that.
Following is my code:-
import React, { Component } from "react";
import { Chip, TextField } from "#material-ui/core";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default class MultiSelect extends Component {
state = {
table: "",
tableData: [],
data: {
table1: ["col1", "col2", "col3", "col4", "col5"],
table2: ["cola", "colb", "colc", "cold", "cole"]
},
columnData: []
};
handleTableChange = (event, value) => {
console.log("Table change value is ", value);
if (this.state.data[value]) {
let columnData = this.state.data[value].map((column) => ({
field: column,
ifSelected: false
}));
console.log("Table change value is ", columnData);
this.setState({ table: value, columnData });
}
};
handleColumnChange = (columns) => {
let data = this.state.columnData;
if (data && columns) {
data.forEach((column) => (column.ifSelected = false));
console.log("Columns are ", columns);
for (let column of columns) {
let index = data.findIndex((item) => item.field === column.field);
console.log("index is ", index);
if (index !== -1) data[index].ifSelected = true;
}
this.setState({ columnData: data });
}
};
componentDidMount() {
let tableData = Object.keys(this.state.data);
this.setState({ tableData });
this.handleTableChange("table1");
}
render() {
return (
<>
<Autocomplete
id="table"
options={this.state.tableData}
getOptionLabel={(option) => option}
renderInput={(params) => (
<TextField {...params} variant="outlined" margin="normal" />
)}
value={this.state.table}
onChange={this.handleTableChange}
/>
<Autocomplete
disabled={!this.state.table}
style={{ marginTop: "1rem" }}
multiple
disableCloseOnSelect
limitTags={3}
options={this.state.columnData}
getOptionLabel={(option) => option.field}
renderInput={(params) => (
<TextField
style={{ fontWeight: "700" }}
{...params}
variant="outlined"
/>
)}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
key={option}
label={option.field}
{...getTagProps({ index })}
/>
))
}
onChange={(event, newValues) => this.handleColumnChange(newValues)}
/>
</>
);
}
}
Would appreciate any hint. I have added demo here
You can fix the issue by passing the value prop to select the columns with ifSelected set to true.
<Autocomplete
value={this.state.columnData.filter((column) => column.ifSelected)}
// other props
/>
CodeSandbox

Disable backspace deleting of options in Autocomplete

I want to use an Autocomplete (from the Material-Ui library) component from material ui to select multiple options, and those options should not be able to be removed (directly) by the user.
The problem I'm facing is that the user can delete the options from the Autocomplete if they focus the component and press backspace as if they are deleting text.
Code
This is the component I'm using:
<Autocomplete multiple
options={options}
getOptionLabel={option => option.title}
renderInput={params =>
<TextField {...params} label="Autocomplete" variant="outlined" />
}
onChange={this.onAutocompleteChange.bind(this)}
getOptionSelected={(option: Option, value: Option) => option.value === value.value}
filterSelectedOptions={true}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option.value} label={option.title} color="primary" />
))
}
disableClearable={true}
/>
What I tried
Disabling the TextField from the renderInput prop with disable={true} has no effect.
Adding InputProps={{ disabled: true }} or InputProps={{ readOnly: true }} to TextField disables the Autocomplete completely.
Adding ChipProps={{ disabled: true }} to the Autocomplete has no effect.
Thanks for reading!
To control this aspect, you need to use a controlled approach to the Autocomplete's value as demonstrated in this demo.
In the documentation for the onChange prop you will find the following:
onChange Callback fired when the value changes.
Signature:
function(event: object, value: T | T[], reason: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "create-option", "select-option", "remove-option", "blur" or "clear".
The third argument to onChange is the "reason" for the change. In your case, you want to ignore all of the "remove-option" changes:
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
Here's a full working example:
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Chip from "#material-ui/core/Chip";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState([options[0]]);
const [inputValue, setInputValue] = React.useState("");
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
disableClearable
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option} label={option} color="primary" />
))
}
/>
</div>
);
}

after entering the value in the textbox if I hit enter, should see a new chip with the entered value in it

when I click textbox text a menu opens, in that textbox I need to enter some value in the textbox.
after entering the value in the textbox if I hit enter, I should see a new chip with the entered value in it.
so I thought I will create an Onchange event and pass the values to the chip component
but the values are not getting passed.
I think I need to pass values an object to that array.
chipData.push(handleTextBox);
console.log("handleTextBox after push chipData--->", chipData);
can you tell me how to fix it.
providing my code snippet and sandbox below.
https://codesandbox.io/s/material-demo-99sf8
demo.js
const [chipData, setChipData] = React.useState([
{ key: 0, label: "Angular" },
{ key: 1, label: "jQuery" },
{ key: 2, label: "Polymer" },
{ key: 3, label: "React" },
{ key: 4, label: "Vue.js" }
]);
const [hideChip, setHideChip] = React.useState([false]);
const handleClick = event => {
setAnchorEl({ type: "icon", target: event.currentTarget });
};
const handleClickFilter = event => {
setAnchorEl({ type: "textbox", target: event.currentTarget });
};
const handleTextBox = event => {
console.log("handleTextBox event--->", event);
console.log("handleTextBox event.target.value--->", event.target.value);
};
console.log("handleTextBox handleTextBox--->", handleTextBox);
console.log("handleTextBox chipData--->", chipData);
chipData.push(handleTextBox);
console.log("handleTextBox after push chipData--->", chipData);
<Menu
id="simple-menu"
anchorEl={
anchorEl && anchorEl.type === "textbox" && anchorEl.target
}
open={Boolean(anchorEl && anchorEl.type === "textbox")}
onClose={handleClose}
>
<MenuItem>
<form
className={classes.container}
noValidate
autoComplete="off"
>
<TextField
id="standard-name"
label="Name"
className={classes.textField}
onChange={handleTextBox}
// value={values.name}
// onChange={handleChange('name')}
margin="normal"
/>
</form>
</MenuItem>
</Menu>
<Paper className={classes.root}>
{chipData.map(data => {
let icon;
console.log("setHideChip--->", setHideChip);
if (data.label === "React") {
icon = <TagFacesIcon />;
}
return (
<Chip
key={data.key}
icon={icon}
label={data.label}
onDelete={handleDelete(data)}
className={classes.chip}
/>
);
})}
</Paper>
try with this code .
<TextField
fullWidth={true}
id="standard-name"
label="Tags"
className={classes.textField}
onChange={handleChange('tag')}
onKeyDown={(e) => pushToarray(e)}
margin="dense"
/>
on entering you should see tag being added
function pushToarray(e){
if(e.key == 'Enter'){
e.preventDefault();
setValues({ tags : [...values.tags ,e.target.value] ,
tag : ''})
}
}
and normal handleChange of text change in the box :
const handleChange = name => event => {
setValues({ ...values, [name]: event.target.value });
console.log(values)
};
Initialize state like this I have initialized with empty array you can use keys:
const [values, setValues] = React.useState({
tag :'',
tags: [],
})
here is your handleDelete function to remove chips :
function handleDelete(item){
var rm = values.tags.indexOf(item);
values.tags.splice( rm, 1)
setValues( { tags : values.tags})
}
this is how your chip component should be
<Paper className={classes.root}>
{
values.tags && values.tags.map( (tag ,id) =>(
<Chip color="default" className={classes.chip} onDelete={() => handleDelete(tag)} key={id} label={tag}/>
))
}

Categories

Resources