AutocompleteArrayInput inside ReferenceArrayInput causes error with flowing report:
in Connect(ReferenceArrayInputController) (created by translate(Connect(ReferenceArrayInputController)))
in translate(Connect(ReferenceArrayInputController)) (created by ReferenceArrayInput)
in ReferenceArrayInput (created by translate(ReferenceArrayInput))
in translate(ReferenceArrayInput) (created by ConnectedField)
in ConnectedField (created by Connect(ConnectedField))
in Connect(ConnectedField) (created by Field)
in Field (created by FormFieldView)
in FormFieldView (created by DefaultValue)
in DefaultValue (created by Connect(DefaultValue))
in Connect(DefaultValue) (created by WithFormField)
in WithFormField (at Create.js:19)
in div (created by FormInput)
in FormInput (created by WithStyles(FormInput))
in WithStyles(FormInput) (created by SimpleForm)
in div (created by CardContent)
in CardContent (created by WithStyles(CardContent))
in WithStyles(CardContent) (created by CardContentInner)
in CardContentInner (created by WithStyles(CardContentInner))
in WithStyles(CardContentInner) (created by SimpleForm)
in form (created by SimpleForm)
in SimpleForm (created by Form(SimpleForm))
in Form(SimpleForm) (created by Connect(Form(SimpleForm)))
in Connect(Form(SimpleForm)) (created by ReduxForm)
in ReduxForm (created by translate(ReduxForm))
in translate(ReduxForm) (created by Connect(translate(ReduxForm)))
in Connect(translate(ReduxForm)) (at Create.js:16)
in div (created by Paper)
in Paper (created by WithStyles(Paper))
in WithStyles(Paper) (created by Card)
in Card (created by WithStyles(Card))
in WithStyles(Card) (created by CreateView)
in div (created by CreateView)
in CreateView (created by CreateController)
in CreateController (created by translate(CreateController))
in translate(CreateController) (created by Connect(translate(CreateController)))
in Connect(translate(CreateController)) (created by Create)
in Create (created by WithStyles(Create))
in WithStyles(Create) (at Create.js:15)
in Unknown (created by WithPermissions)
in WithPermissions (created by Connect(WithPermissions))
in Connect(WithPermissions) (created by getContext(Connect(WithPermissions)))
in getContext(Connect(WithPermissions)) (created by Route)
in Route (created by Resource)
in Switch (created by Resource)
in Resource (created by Connect(Resource))
in Connect(Resource) (at App.js:49)
in Route (created by RoutesWithLayout)
in Switch (created by RoutesWithLayout)
in RoutesWithLayout (created by Route)
in div (created by Layout)
in main (created by Layout)
in div (created by Layout)
in div (created by Layout)
in Layout (created by WithStyles(Layout))
in WithStyles(Layout) (created by Route)
in Route (created by withRouter(WithStyles(Layout)))
in withRouter(WithStyles(Layout)) (created by Connect(withRouter(WithStyles(Layout))))
in Connect(withRouter(WithStyles(Layout))) (created by LayoutWithTheme)
in MuiThemeProvider (created by LayoutWithTheme)
in LayoutWithTheme (created by Route)
in Route (created by CoreAdminRouter)
in Switch (created by CoreAdminRouter)
in div (created by CoreAdminRouter)
in CoreAdminRouter (created by Connect(CoreAdminRouter))
in Connect(CoreAdminRouter) (created by getContext(Connect(CoreAdminRouter)))
in getContext(Connect(CoreAdminRouter)) (created by Route)
in Route (created by CoreAdmin)
in Switch (created by CoreAdmin)
in Router (created by ConnectedRouter)
in ConnectedRouter (created by CoreAdmin)
in TranslationProvider (created by withContext(TranslationProvider))
in withContext(TranslationProvider) (created by Connect(withContext(TranslationProvider)))
in Connect(withContext(TranslationProvider)) (created by CoreAdmin)
in Provider (created by CoreAdmin)
in CoreAdmin (created by withContext(CoreAdmin))
in withContext(CoreAdmin) (at App.js:45)
in App (at index.js:7)
If you are able to illustrate the bug or feature request with an example, please provide a sample application via one of the following means:
Same issue here
having same issues.
I think error is coming from autocompletearrayinput, in input.value when it is initializing for first time and it's default value is null, not [], so when its adding first item input.value is not an array its a single value, i've fixed this issue by editing autocompletearrayinput.js and force input.value to be an array by adding this line:
inputValue: Array.isArray( input.value) ? input.value : [ input.value] , in every line that we are reading input.value
for a temporary hot fix replace node_modules\ra-ui-materialui\esm\input\AutocompleteArrayInput.js with flowing codes:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import Autosuggest from 'react-autosuggest';
import Chip from '@material-ui/core/Chip';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem';
import { withStyles } from '@material-ui/core/styles';
import parse from 'autosuggest-highlight/parse';
import match from 'autosuggest-highlight/match';
import blue from '@material-ui/core/colors/blue';
import compose from 'recompose/compose';
import classNames from 'classnames';
import { addField, translate, FieldTitle } from 'ra-core';
import AutocompleteArrayInputChip from './AutocompleteArrayInputChip';
var styles = function (theme) { return ({
container: {
flexGrow: 1,
position: 'relative',
},
root: {},
suggestionsContainerOpen: {
position: 'absolute',
marginBottom: theme.spacing.unit * 3,
zIndex: 2,
},
suggestion: {
display: 'block',
fontFamily: theme.typography.fontFamily,
},
suggestionText: { fontWeight: 300 },
highlightedSuggestionText: { fontWeight: 500 },
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
chip: {
marginRight: theme.spacing.unit,
},
chipDisabled: {
pointerEvents: 'none',
},
chipFocused: {
backgroundColor: blue[300],
},
}); };
/**
* An Input component for an autocomplete field, using an array of objects for the options
*
* Pass possible options as an array of objects in the 'choices' attribute.
*
* By default, the options are built from:
* - the 'id' property as the option value,
* - the 'name' property an the option text
* @example
* const choices = [
* { id: 'M', name: 'Male' },
* { id: 'F', name: 'Female' },
* ];
* <AutocompleteInput source="gender" choices={choices} />
*
* You can also customize the properties to use for the option name and value,
* thanks to the 'optionText' and 'optionValue' attributes.
* @example
* const choices = [
* { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
* { _id: 456, full_name: 'Jane Austen', sex: 'F' },
* ];
* <AutocompleteInput source="author_id" choices={choices} optionText="full_name" optionValue="_id" />
*
* `optionText` also accepts a function, so you can shape the option text at will:
* @example
* const choices = [
* { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
* { id: 456, first_name: 'Jane', last_name: 'Austen' },
* ];
* const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
* <AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
*
* The choices are translated by default, so you can use translation identifiers as choices:
* @example
* const choices = [
* { id: 'M', name: 'myroot.gender.male' },
* { id: 'F', name: 'myroot.gender.female' },
* ];
*
* However, in some cases (e.g. inside a `<ReferenceInput>`), you may not want
* the choice to be translated. In that case, set the `translateChoice` prop to false.
* @example
* <AutocompleteInput source="gender" choices={choices} translateChoice={false}/>
*
* The object passed as `options` props is passed to the material-ui <AutoComplete> component
*
* @example
* <AutocompleteInput source="author_id" options={{ fullWidth: true }} />
*/
var AutocompleteArrayInput = /** @class */ (function (_super) {
__extends(AutocompleteArrayInput, _super);
function AutocompleteArrayInput() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
dirty: false,
inputValue: null,
searchText: '',
suggestions: [],
};
_this.inputEl = null;
_this.getSuggestionValue = function (suggestion) { return get(suggestion, _this.props.optionValue); };
_this.getSuggestionText = function (suggestion) {
if (!suggestion)
return '';
var _a = _this.props, optionText = _a.optionText, translate = _a.translate, translateChoice = _a.translateChoice;
var suggestionLabel = typeof optionText === 'function'
? optionText(suggestion)
: get(suggestion, optionText);
// We explicitly call toString here because AutoSuggest expect a string
return translateChoice
? translate(suggestionLabel, { _: suggestionLabel }).toString()
: suggestionLabel.toString();
};
_this.handleSuggestionSelected = function (event, _a) {
var suggestion = _a.suggestion, method = _a.method;
var input = _this.props.input;
input.onChange(_this.state.inputValue.concat([
_this.getSuggestionValue(suggestion),
]));
if (method === 'enter') {
event.preventDefault();
}
};
_this.handleSuggestionsFetchRequested = function () {
var _a = _this.props, choices = _a.choices, inputValueMatcher = _a.inputValueMatcher;
_this.setState(function (_a) {
var searchText = _a.searchText;
return ({
suggestions: choices.filter(function (suggestion) {
return inputValueMatcher(searchText, suggestion, _this.getSuggestionText);
}),
});
});
};
_this.handleSuggestionsClearRequested = function () {
_this.updateFilter('');
};
_this.handleMatchSuggestionOrFilter = function (inputValue) {
_this.setState({
dirty: true,
searchText: inputValue,
});
_this.updateFilter(inputValue);
};
_this.handleChange = function (event, _a) {
var newValue = _a.newValue, method = _a.method;
switch (method) {
case 'type':
case 'escape':
{
_this.handleMatchSuggestionOrFilter(newValue);
}
break;
}
};
_this.renderInput = function (inputProps) {
var input = _this.props.input;
var autoFocus = inputProps.autoFocus, className = inputProps.className, classes = inputProps.classes, isRequired = inputProps.isRequired, label = inputProps.label, meta = inputProps.meta, onChange = inputProps.onChange, resource = inputProps.resource, source = inputProps.source, value = inputProps.value, ref = inputProps.ref, _a = inputProps.options, InputProps = _a.InputProps, options = __rest(_a, ["InputProps"]), other = __rest(inputProps, ["autoFocus", "className", "classes", "isRequired", "label", "meta", "onChange", "resource", "source", "value", "ref", "options"]);
if (typeof meta === 'undefined') {
throw new Error("The TextInput component wasn't called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details.");
}
var touched = meta.touched, error = meta.error, _b = meta.helperText, helperText = _b === void 0 ? false : _b;
// We need to store the input reference for our Popper element containg the suggestions
// but Autosuggest also needs this reference (it provides the ref prop)
var storeInputRef = function (input) {
_this.inputEl = input;
ref(input);
};
return (React.createElement(AutocompleteArrayInputChip, __assign({ clearInputValueOnChange: true, onUpdateInput: onChange, onAdd: _this.handleAdd, onDelete: _this.handleDelete, value: Array.isArray(input.value) ? input.value : input.value ? [input.value] : [] , inputRef: storeInputRef, error: touched && error, helperText: touched && error && helperText, chipRenderer: _this.renderChip, label: React.createElement(FieldTitle, { label: label, source: source, resource: resource, isRequired: isRequired }) }, other, options)));
};
_this.renderChip = function (_a, key) {
var value = _a.value, isFocused = _a.isFocused, isDisabled = _a.isDisabled, handleClick = _a.handleClick, handleDelete = _a.handleDelete;
var _b;
var _c = _this.props, _d = _c.classes, classes = _d === void 0 ? {} : _d, choices = _c.choices;
var suggestion = choices.find(function (choice) { return _this.getSuggestionValue(choice) === value; });
return (React.createElement(Chip, { key: key, className: classNames(classes.chip, (_b = {},
_b[classes.chipDisabled] = isDisabled,
_b[classes.chipFocused] = isFocused,
_b)), onClick: handleClick, onDelete: handleDelete, label: _this.getSuggestionText(suggestion) }));
};
_this.handleAdd = function (chip) {
var _a = _this.props, choices = _a.choices, input = _a.input, limitChoicesToValue = _a.limitChoicesToValue, inputValueMatcher = _a.inputValueMatcher;
var filteredChoices = choices.filter(function (choice) {
return inputValueMatcher(chip, choice, _this.getSuggestionText);
});
var choice = filteredChoices.length === 1
? filteredChoices[0]
: filteredChoices.find(function (c) { return _this.getSuggestionValue(c) === chip; });
if (choice) {
return input.onChange(_this.state.inputValue.concat([
_this.getSuggestionValue(choice),
]));
}
if (limitChoicesToValue) {
// Ensure to reset the filter
_this.updateFilter('');
return;
}
input.onChange(_this.state.inputValue.concat([chip]));
};
_this.handleDelete = function (chip) {
var input = _this.props.input;
input.onChange(_this.state.inputValue.filter(function (value) { return value !== chip; }));
};
_this.renderSuggestionsContainer = function (options) {
var _a = options.containerProps, className = _a.className, containerProps = __rest(_a, ["className"]), children = options.children;
return (React.createElement(Popper, { className: className, open: true, anchorEl: _this.inputEl, placement: "bottom-start" },
React.createElement(Paper, __assign({ square: true }, containerProps), children)));
};
_this.renderSuggestionComponent = function (_a) {
var suggestion = _a.suggestion, query = _a.query, isHighlighted = _a.isHighlighted, props = __rest(_a, ["suggestion", "query", "isHighlighted"]);
return React.createElement("div", __assign({}, props));
};
_this.renderSuggestion = function (suggestion, _a) {
var query = _a.query, isHighlighted = _a.isHighlighted;
var label = _this.getSuggestionText(suggestion);
var matches = match(label, query);
var parts = parse(label, matches);
var _b = _this.props, _c = _b.classes, classes = _c === void 0 ? {} : _c, suggestionComponent = _b.suggestionComponent;
return (React.createElement(MenuItem, { selected: isHighlighted, component: suggestionComponent || _this.renderSuggestionComponent, suggestion: suggestion, query: query, isHighlighted: isHighlighted },
React.createElement("div", null, parts.map(function (part, index) {
return part.highlight ? (React.createElement("span", { key: index, className: classes.highlightedSuggestionText }, part.text)) : (React.createElement("strong", { key: index, className: classes.suggestionText }, part.text));
}))));
};
_this.handleFocus = function () {
var input = _this.props.input;
input && input.onFocus && input.onFocus();
};
_this.updateFilter = function (value) {
var _a = _this.props, setFilter = _a.setFilter, choices = _a.choices;
if (_this.previousFilterValue !== value) {
if (setFilter) {
setFilter(value);
}
else {
_this.setState({
searchText: value,
suggestions: choices.filter(function (choice) {
return _this.getSuggestionText(choice)
.toLowerCase()
.includes(value.toLowerCase());
}),
});
}
}
_this.previousFilterValue = value;
};
_this.shouldRenderSuggestions = function () { return true; };
return _this;
}
AutocompleteArrayInput.prototype.componentWillMount = function () {
this.setState({
inputValue:Array.isArray( this.props.input.value) ? this.props.input.value : this.props.input.value ? [ this.props.input.value]:[],
suggestions: this.props.choices,
});
};
AutocompleteArrayInput.prototype.componentWillReceiveProps = function (nextProps) {
var _this = this;
var choices = nextProps.choices, input = nextProps.input, inputValueMatcher = nextProps.inputValueMatcher;
if (!isEqual(input.value, this.state.inputValue)) {
this.setState({
inputValue: Array.isArray(input.value) ? input.value : input.value ? [input.value] : [],
dirty: false,
suggestions: this.props.choices,
});
// Ensure to reset the filter
this.updateFilter('');
}
else if (!isEqual(choices, this.props.choices)) {
this.setState(function (_a) {
var searchText = _a.searchText;
return ({
suggestions: choices.filter(function (suggestion) {
return inputValueMatcher(searchText, suggestion, _this.getSuggestionText);
}),
});
});
}
};
AutocompleteArrayInput.prototype.render = function () {
var _a = this.props, alwaysRenderSuggestions = _a.alwaysRenderSuggestions, _b = _a.classes, classes = _b === void 0 ? {} : _b, isRequired = _a.isRequired, label = _a.label, meta = _a.meta, resource = _a.resource, source = _a.source, className = _a.className, options = _a.options;
var _c = this.state, suggestions = _c.suggestions, searchText = _c.searchText;
return (React.createElement(Autosuggest, { theme: {
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}, renderInputComponent: this.renderInput, suggestions: suggestions, alwaysRenderSuggestions: alwaysRenderSuggestions, onSuggestionSelected: this.handleSuggestionSelected, onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested, onSuggestionsClearRequested: this.handleSuggestionsClearRequested, renderSuggestionsContainer: this.renderSuggestionsContainer, getSuggestionValue: this.getSuggestionText, renderSuggestion: this.renderSuggestion, shouldRenderSuggestions: this.shouldRenderSuggestions, inputProps: {
blurBehavior: 'add',
className: className,
classes: classes,
isRequired: isRequired,
label: label,
meta: meta,
onChange: this.handleChange,
resource: resource,
source: source,
value: searchText,
onFocus: this.handleFocus,
options: options,
} }));
};
return AutocompleteArrayInput;
}(React.Component));
export { AutocompleteArrayInput };
AutocompleteArrayInput.propTypes = {
allowEmpty: PropTypes.bool,
alwaysRenderSuggestions: PropTypes.bool,
choices: PropTypes.arrayOf(PropTypes.object),
classes: PropTypes.object,
className: PropTypes.string,
InputProps: PropTypes.object,
input: PropTypes.object,
inputValueMatcher: PropTypes.func,
isRequired: PropTypes.bool,
label: PropTypes.string,
limitChoicesToValue: PropTypes.bool,
meta: PropTypes.object,
options: PropTypes.object,
optionText: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
.isRequired,
optionValue: PropTypes.string.isRequired,
resource: PropTypes.string,
setFilter: PropTypes.func,
source: PropTypes.string,
suggestionComponent: PropTypes.func,
translate: PropTypes.func.isRequired,
translateChoice: PropTypes.bool.isRequired,
};
AutocompleteArrayInput.defaultProps = {
choices: [],
options: {},
optionText: 'name',
optionValue: 'id',
limitChoicesToValue: false,
translateChoice: true,
inputValueMatcher: function (input, suggestion, getOptionText) {
return getOptionText(suggestion)
.toLowerCase()
.trim()
.includes(input.toLowerCase().trim());
},
};
export default compose(addField, translate, withStyles(styles))(AutocompleteArrayInput);
@Masoodt Thanks for the tip! I found a simpler fix: add defaultValue={[]} to the ReferenceArrayInput
Again, we can't fix the issue if you don't provide a clear way to reproduce it. Please add more details (code sample, CodeSandbox).
@kuler90 your solution doesn't work for me
@Masoodt your code update is good! And this component finally working! Thanks ;)
Edit: Found one bug, when input is empty, it always reset autocomplete query to blank, so after few first letters return valid filtered results, second after that query for empty filter. When one item is selected, then it worked ok.
@Mattin I'm glad to hear that.
@fzaninotto @djhi it can be replicated when you go to Create a Post and click on one of the tag options:
https://codesandbox.io/s/vq4l7klm8y. Note this doesn't happen in Edit mode!
A possibly related bug with filter={} and free text search: https://github.com/marmelab/react-admin/issues/2559
I just upgraded to [email protected] to see if it fixed the issue completely, but I still have to patch it up with @Masoodt 's solution
@Mattin I can confirm the empty input bug on [email protected] and [email protected]. As soon as I select an option it works fine.
Fixed by #2616
Most helpful comment
I just upgraded to [email protected] to see if it fixed the issue completely, but I still have to patch it up with @Masoodt 's solution
@Mattin I can confirm the empty input bug on [email protected] and [email protected]. As soon as I select an option it works fine.