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
Related
I am working on a react project where I am using react-virtualized to render a table with columns and I am using a cellRenderer to render data in different cells but I want a Input field in one of the cells so I created a custom cellRenderer to return that component but in the table its showing as object.
Here's my code:
codeDataGetter = ({
dataKey,
rowData,
}) => {
let dataToReturn = '';
if (rowData) {
if (typeof rowData.get === 'function') {
dataToReturn = rowData.get(dataKey);
}
dataToReturn = rowData[dataKey];
} else dataToReturn = '';
console.log('codeDataGetter', dataToReturn);
return (
<Input
type='text'
className='form-control'
value={dataToReturn}
placeholder={translateText('Code')}
onChange={() => this.handleCodeChange(event, index)}
style={{ height: '70%' }}
/>
);
}
Table:
<StyledTable
height={this.props.height}
width={this.props.width}
headerHeight={headerHeight}
rowHeight={rowHeight}
rowRenderer={this.rowRenderer}
rowCount={this.props.codeSets.length}
rowGetter={({ index }) => this.props.codeSets[index]}
LoadingRow={this.props.LoadingRow}
overscanRowCount={5}
tabIndex={-1}
className='ui very basic small single line striped table'
columnsList={columns}
/>
Columns:
const columns = (
[ <Column
key={3}
label='Code'
dataKey='code'
width={widthOfEachGrid * 1}
cellDataGetter={this.codeDataGetter}
/>,
<Column
key={4}
label='Code Description'
dataKey='code_description'
width={widthOfEachGrid * 2}
cellDataGetter={this.codeDescriptionDataGetter}
/>,
]
);
Object... is getting shown in place of Input Box.Any help will be appreciated.
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>
);
}
I'm using the material ui autocomplete component but I noticed that when the chips (tags) are deleted directly from the (x) button on the chip the autocomplete's onchange function is not triggered. Any ideas how I can get the onchange to trigger when a tag is deleted directly from the chip component?
Bellow is my code:
My component which uses autocomplete
export default function FormSearchInput( props ) {
const classes = FormStyle();
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
return (
<Grid item xs = {props.xs} className = {classes.formItem}>
<Autocomplete
className = {props.className}
size = {props.size}
limitTags = {4}
multiple
options = {props.options}
disableCloseOnSelect
getOptionLabel = {( option ) => option}
defaultValue = {props.selectedOptions}
renderOption = {( option, {selected} ) => (
<React.Fragment>
<Checkbox
icon = {icon}
checkedIcon = {checkedIcon}
style = {{ marginRight: 8 }}
checked = {selected}
onChange = {props.onChange( option, selected )}
/>
{option}
</React.Fragment>
)}
renderInput = {( params ) => (
<TextField {...params} variant = "outlined" label = {props.label}/>
)}
/>
</Grid>
)
}
My onchange handler which i pass to my formsearchcomponent:
function handleCollaboratorsChange( option, selected ) {
console.log("triggering")
let tempCollaborators = collaborators
if( selected && !tempCollaborators.includes(option) ) {
// If collaborator not in list add to list
tempCollaborators.push( option )
} else if( !selected && tempCollaborators.includes(option) ) {
// If collaborator in list remove from list
tempCollaborators.splice( tempCollaborators.indexOf(option), 1 );
}
setCollaborators( tempCollaborators )
}
Capture the "reason" in the onchange.
In the following example, I've an autocomplete that allows the user to add new options and display them as chips.
// HANDLE: ON CHANGE
const on_change = (
event: React.SyntheticEvent,
newValue: any,
reason: string,
details: any,
) => {
const selected_item = newValue[newValue.length - 1]
switch (reason) {
case 'removeOption':
case 'remove-option':
if (details.option.name) {
// remove an existing option
remove_tag(details.option.name)
} else {
// remove a new created option
remove_tag(details.option.inputValue)
}
break
case 'createOption':
case 'selectOption':
if (typeof selected_item === 'string') {
if (can_add()) create_tag(newValue)
} else if (selected_item && selected_item.inputValue) {
// Create a new value from the user input
if (can_add()) create_tag(selected_item.inputValue)
} else {
if (can_add()) {
if (!tags.includes(selected_item)) set_tag([...tags, selected_item])
}
}
break
}
}
And define the component like this:
<Autocomplete
multiple={true}
autoHighlight={true}
limitTags={tags_limit}
id="cmb_tags"
options={full_tags}
getOptionLabel={on_get_option_label}
defaultValue={tags}
freeSolo
filterOptions={on_filter}
selectOnFocus
noOptionsText={i18n.t('sinopciones')}
onChange={on_change}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder={placeholder}
/>
)}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</li>
)}
/>
Let me know if this helps you.
Add the onChange property...
<Autocomplete
className = {props.className}
size = {props.size}
limitTags = {4}
multiple
options = {props.options}
disableCloseOnSelect
getOptionLabel = {( option ) => option}
defaultValue = {props.selectedOptions}
**onChange = {(event, newValue) => { handleCollaboratorsChange(newValue); }}**
renderOption = {( option, {selected} ) => (
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 />
)}
/>
I'm using React with Material-ui and the Autocomplete component documented here - https://material-ui.com/components/autocomplete/ with downshift.
<Downshift id="downshift-options">
{({
clearSelection,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
openMenu,
selectedItem,
}) => {
const {onSelect, onBlur, onChange, onFocus, ...inputProps} = getInputProps({
onChange: event => {
if (event.target.value === '') {
clearSelection();
}
},
onSelect: event => {
if (event.target.id) {
this.props.onSelect(event.target.value);
}
},
onFocus: openMenu,
placeholder: 'Type to search',
});
return (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
label: "Assigned Rider",
InputLabelProps: getLabelProps({shrink: true}),
InputProps: {onBlur, onChange, onFocus, onSelect},
inputProps,
})}
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{getSuggestions(this.props.suggestions, inputValue, {showEmpty: true}).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({item: suggestion.label}),
highlightedIndex,
selectedItem,
}),
)}
</Paper>
) : null}
</div>
</div>
);
}}
</Downshift>
With onSelect, I can retrieve the value of the selection. I'd like to be able to retrieve the key instead.
function renderSuggestion(suggestionProps) {
const { suggestion, index, itemProps, highlightedIndex, selectedItem } = suggestionProps;
const isHighlighted = highlightedIndex === index;
const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1;
return (
<MenuItem
{...itemProps}
key={suggestion.uuid}
value={suggestion.uuid}
selected={isHighlighted}
component="div"
style={{
fontWeight: isSelected ? 500 : 400,
}}
>
{suggestion.label}
</MenuItem>
);
}
Here I set the uuid as the key for each selection.
My ultimate aim is to be able to make a selection and retrieve a uuid instead of the value itself.
Although I can use the value returned to match against a list of items, I want to be sure that if there end up being any duplicate entries, it doesn't cause problems.
Link to my full code for the component is here - https://github.com/theocranmore/bloodbike/blob/master/react_app/src/components/UsersSelect.js#L143
Thank you!
I don't know why you need an id but for my own getting the object itself will suffice.
<Autocomplete .....
onChange={(event, newValue) => {
console.log(newValue); //this will give you the selected value dictionary (source)
}}
You can retrieve the entire value and then access your desired key (option.id):
const options = [
{ id: 0, value: "foo" },
{ id: 1, value: "goo" },
];
<Autocomplete
options={options}
onChange={(event, option) => {
console.log(option.id); // 1
}}
renderInput={(params) => <TextField {...params} />}
/>;