Autocomplete - MaterialUI - Controled component doesn't work - reactjs

Basically, I created the autocomplete component and set default value as my state this.state.quest.ansType, but when I change this state, the component result an error: component is changing the default value state of an uncontrolled Autocomplete after being initialized. To suppress this warning opt to use a controlled Autocomplete.
I need this for my update function. When the user selects the register on the database, I will load the options saved on the default value of Autocomplete.
const ansTypes = [
{
id: 'T',
desc: "Texto"
},
{
id: 'M',
desc: "Multipla Escolha"
},
{
id: 'U',
desc: "Escolha Única"
},
];
<Autocomplete className="cb" id={"ansType"} options={ansTypes}
disableCloseOnSelect
onChange={obj => this.selectAnsType(obj)}
defaultValue={this.state.quest.ansType}
getOptionLabel={option => option.desc}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
style={{ marginRight: 8 }}
checked={selected}
color={"primary"}
/>
{option.desc}
</React.Fragment>
)}
renderInput={(params) => (<TextField {...params} label={"Answer Type"} />)}
/>

Basically, you've been changing the default value after the first render, which shouldn't happen. Either you use an uncontrolled Component (-onChange, -value,+defaultValue, +ref) of you use a controlled component (+onChange, +value, -defaultValue, ref->only if needed).
DefaultValue should be used for the uncontrolled ones!
Controlled
...
<Autocomplete className="cb" id={"ansType"} options={ansTypes}
...
onChange={obj => this.selectAnsType(obj)}
value={this.state.quest.ansType}
...
/>
Uncontrolled
...
defaultAnsType={...};
myUncontrolledAutocomplete=React.createRef();
<Autocomplete className="cb" id={"ansType"} options={ansTypes}
...
defaultValue={this.defaultAnsType}
ref={this.myUncontrolledAutocomplete}
...
/>
$
Personal Opinion
I'd use the controlled one, because it's easier to understand. On the other hand the uncontrolled component could make your component stateless which could be something you might wanna use in the future.

Related

Creatable Select from react-select/creatable with API integration in react-final-form

I have successfully integrated the creatable with react-final-form but the problem arises when i try to do an API call. I am trying to do an API call with onIputChange but whenever i get a response(in props), my drop down closes. Its closing up on receiving the new options. I tried it with normal select and it works fine. Here is my example code.
<Field
name="tags"
component={ ({
input,
...rest
}) => (<CreatableSelect {...input}
{...rest}}
/>)}
isMulti
options={sortedTagsOptions}
onInputChange={(x) => handleChangeTypeheadInput(x)}/>
The API call works fine. Only one issue occurs, the dropdown closes and goes out of focus as well as the written value goes away in the select. If anyone has any idea on how to deal with this problem please let me know. This works fine without Field component though. Maybe mutate the form values? but having problems in that regard as well.
Solved it by creating a separate component rather than doing it all in one component.
const AppCreateableSelectForm = ( {
input: {
name, onChange, value,
},
className,
incomingOptions,
isClearable,
isMulti,
onFocus,
closeMenuOnSelect,
incomingStyle,
isDisabled,
placeholder,
onInputChange,
noOptionsMessage,
}) => {
return <CreatableSelect
name={name}
isMulti={isMulti}
onFocus={onFocus}
closeMenuOnSelect={closeMenuOnSelect}
options={incomingOptions}
isClearable={isClearable}
placeholder={placeholder ? placeholder:`Select`}
className={className}
isDisabled={isDisabled}
styles={incomingStyle ? incomingStyle : style}
value={value}
onChange={onChange}
onInputChange={onInputChange}
noOptionsMessage={noOptionsMessage}
theme={theme => ({
...theme,
colors: {
...theme.colors,
primary: `#637282`,
},
})}
components={{
IndicatorSeparator: () => null,
}}
/>
Some props might not be used in this code since i copied only relative code.

Material-ui autocomplete clear value

I have one problem in my react code.
I use Material-ui and redux-form. I have select input like and after change this select i should reset value in . I use action 'change' from react-form and set value for textfield. But label in still remains. Can i clear or reset value in ?
<Autocomplete
options={list}
getOptionLabel={option => option.name}
onInputChange={onChange}
onChange={onChangeAutoComplete}
noOptionsText='Нет доступных вариантов'
loadingText='Загрузка...'
openText='Открыть'
renderInput={params => (
<Field
{...params}
label={label}
name={fieldName}
variant="outlined"
fullWidth
component={renderTextField}
className={classes.textField}
margin="normal"
/>
)}
/>
Using hooks on the value prop breaks the functionality of the autocomplete component ( at least for me ). Using class, and setting the local state is the same.
Luckily it is a react component, so it have a "key" prop. When the key prop changes, the component is re-rendered with the default values ( which is an empty array since nothing is selected). I used hooks in the parent component and passed the values to the key prop, whenever reset is needed.
<Autocomplete
key={somethingMeaningful} // Bool, or whatever just change it to re-render the component
//...other props
/>
Hope this helps!
Material UI Autocomplete onInputChange callback provides reason argument. If input has been changed by input, reason will be input and if you selected option then reason will be reset.
onInputChange={(event, newInputValue, reason) => {
if (reason === 'reset') {
setValue('')
return
} else {
setValue(newInputValue)
}
}}
setValue is useState and you can pass value state to autocomplete value property.
use value in your <Autocomplete /> like this:
<Autocomplete
value={this.state.value} //insert your state key here
//...other props
/>
Then clear state of that key, to clear the autocomplete field value
I am going to post a very dirty way of clearing the value of Autocomplete. Try it ONLY when nothing else works;
import React, { useRef } from 'react';
...
const autoC = useRef(null);
...
<Autocomplete
...
ref={autoC}
/>
and then when you want to clear the value;
const ele = autoC.current.getElementsByClassName('MuiAutocomplete-clearIndicator')[0];
if (ele) ele.click();
This is what worked for me.
const [name, setName] = useState('');
<Autocomplete
inputValue={name}
onChange={(e,v)=>setName(v?.name||v)}
...
/>
<Button onClick={()=>setName('')}>
Clear
</Button>
You can use something like the following to clear the autocomplete field when an item is selected.
<Autocomplete
value={null}
blurOnSelect={true} />
Note that you may also need to set clearOnBlur={true} if you're using the freeSolo option.
Source https://mui.com/api/autocomplete/#props
I achieved this by updating the inputValue prop where multiple prop is false. If you are using multiple prop, then there is a propblem (bug). Selected values does not get erased.
When I encountered this, it was when options for the autocomplete changed, and wanted to clear the input value. It wouldn't clear with just the options changing. What worked for me is adding a key value onto the autocomplete which depended on the change which necessitated clearing.
To solve this, I created a hook that watches the value state of the autocomplete and set the value of the input if the checkClear returns true;
function useAutocompleteInputClear(watch, checkClear) {
const elmRef = useRef(null);
useMemo(() => {
if (!elmRef || !elmRef.current) return;
if (!checkClear || typeof checkClear !== "function") return;
const button = elmRef.current.querySelector("button")
if (checkClear(watch) && button) {
button.click();
}
}, [watch])
return elmRef;
}
Its first argument is the value that should be watched and its second argument is a function that returns a boolean. if it is true the clearing will happen.
Also, the hook returns a ref that needs to pass as ref prop to Autocomplete.
const elmRef = useAutocompleteInputClear(value, v => !v || !v.id)
<Autocomplete ref={elmRef}
value={value}
...
using onChange property we can clear the value by clicking the clear icon in the following way
<Autocomplete
fullWidth={true}
label={'Source'}
margin={'noraml'}
multiple={false}
name={'Source'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
ref={SourceRef}
value={formValues.Source === '' ? {label: ''} : {label: formValues.Source}}
options={SourceStatus}
onChange={useCallback((e, v) => {
if (typeof v === 'object' && v !== null) {
handleInputChange(e, v) // help to set the value
} else {
handleInputChange(e, {label: ''}) // help to reset the value
}
})}
/>
In my case for multiselect freeSolo onChange props 3rd argument reason solved my all issues.
AutocompleteChangeReason can be:
blur
clear
createOption
removeOption
selectOption
and 2nd arg of this props gives u already updated list of (multiselect) value/s.
onChange={(_event, newOptions, reason) => {
setOptions(
reason === 'clear' ? [] : [...newOptions.map((o) => Number(o))],
);
}}
If you need only the selected value, set the value to an empty object and render the option to your needs.
<Autocomplete
value={{}}
onChange={handleSelectionChanged}
options={options ?? []}
getOptionLabel={x => (!x ? '' : x?.name ?? '')}
renderInput={params => <TextField {...params} label="" />}
/>
If you are using objects, you can use the following code to clear the field.
Ensure you add isOptionEqualToValue:
<Autocomplete
style={{ width: 250 }}
multiple
id="checkboxes-tags-demo"
options={list}
isOptionEqualToValue={(option, newValue) => {
return option.id === newValue.id;
}}
value={selected}
onChange={(e, val) => handleSelected(e, val)}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Add to Multiple"
placeholder="Favorites" />
)} />
Just set an empty array in your state through functions, and it'll be cleared.
Try this method:
use onChange method and pass third parameter reason and compare to clear text if reason is clear then executed this function.
<Autocomplete
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
console.log("Put your clear logic here: this condition executed when clear button clicked")
setValue({ title: '', year: '' }) //for reset the value
return
}
}}
/>
One easy way to do this is to pass these props to autocomplete like this:
onChange={handleSkillChange}
inputValue=''
clearOnBlur={true}
onChange is an event handler, which stores the value in the state.
inputValue='' helps to ensure that the text field inside autocomplete will always be empty
clearOnBlur={true} helps to clear the value of the autocomplete component when it loses focus.

react-rte (Rich Text Editor): how to implement inline style for custom components?

react-rte is a Rich Text Editor based on draft-js. My goal is to customize the toolbar components with, e.g., material ui react components. Reading through the react-rte docs, I think that there are two styling hooks:
toolbarConfig for CSS (link); and
customControls for completely overriding components (as seen in demo).
I believe that my use case calls for customControls, but from the provided demo (see below) I am not able to understand how to hook the custom components back into rte's functionality. For example, if I render a custom button component for BOLD, how does this button get the default functionality that would have gone to the default button provided by toolbarConfig?
editor demo with customControls:
<RichTextEditor
value={value}
onChange={this._onChange}
className="react-rte-demo"
placeholder="Tell a story"
toolbarClassName="demo-toolbar"
editorClassName="demo-editor"
readOnly={this.state.readOnly}
customControls={[
// eslint-disable-next-line no-unused-vars
(setValue, getValue, editorState) => {
let choices = new Map([
['1', {label: '1'}],
['2', {label: '2'}],
['3', {label: '3'}],
]);
return (
<ButtonGroup key={1}>
<Dropdown
choices={choices}
selectedKey={getValue('my-control-name')}
onChange={(value) => setValue('my-control-name', value)}
/>
</ButtonGroup>
);
},
<ButtonGroup key={2}>
<IconButton
label="Remove Link"
iconName="remove-link"
focusOnClick={false}
onClick={() => console.log('You pressed a button')}
/>
</ButtonGroup>,
]}
/>
my currently invalid implementation:
<RichTextEditor
value={this.state.value}
onChange={this.onChange}
customControls={rteCustomControls}
/>
...
const inlineStyleButtonControls = [
{ label: "format_bold", style: "BOLD", component: FormatBoldIcon },
{ label: "format_italic", style: "ITALIC", component: FormatItalicIcon },
{
label: "format_underlined",
style: "UNDERLINE",
component: FormatUnderlinedIcon,
},
];
const rteCustomControls = [
(setValue, getValue, editorState) => {
return inlineStyleButtonControls.map((button, i) => (
<IconButton
key={i}
color="inherit"
aria-label={button.label}
selectedKey={getValue(button.style)}
onClick={value => setValue(button.style, value)}
>
<button.component />
</IconButton>
));
},
];
If your goal is purely to modify the display, you should be able to target the buttons via CSS to change their displays, just like you would target any other DOM element.
Change element display via CSS.
RichTextEditor button:nth-child(1){
background-image: url('/icon1.svg');
}
RichTextEditor button:nth-child(2){
background-image: url('/icon2.svg');
}
Or, change element display via javascript
Array.from(document.querySelectorsAll('RichTextEditor button')).forEach((el)=>{
// Modify element here
})
If you also want to modify the button's functionality, you can look at the code where they are defined, and potentially add them in as custom controls.

Material UI - Change Font Size of TextField from Functional Component

I have the following functional component and I would like to change the font size of the textfield, but for some reason I can't figure it out. I know if I have a traditional component I can export it withStyles and set the className or InputProps, but I'm not sure how to do that with my current setup:
Class Definition:
const FormObjectText = ({id, multiline, onBlur, onChange, onFocus, placeholder, value, style, ...additionalProps}) => (
<TextField
{...additionalProps}
fullWidth
id={id}
inputProps={additionalProps.maxLength != null ? {maxLength: additionalProps.maxLength} : {}}
margin="normal"
multiline={multiline}
onBlur={onBlur}
onChange={e => onChange({ value: e.target.value })}
onFocus={onFocus}
placeholder={placeholder}
style={{
...style
}}
value={value !== null ? value : ""}
/>
);
Export from that file:
export const FORM_OBJECT_DICT = {
text: FormObjectTextStyled,
date: FormObjectDate,
// Others
};
Where it is called in another file:
{FORM_OBJECT_DICT["text"]({
value: editing ? value : getFormObjectDisplayValue(configuration, value),
onChange: this.onChange
})}
Firstly, is there any way to change the font size of the TextField using inline styles (not withStyles()), or if not, where/how would i apply withStyles() in this instance?
You can change the font size with inline styles this way:
<TextField inputProps={{ style: { fontSize: "5rem" } }} />
There is nothing about withStyles that cares whether your component is a function component or a class, so if you want to use classes, you could do something like:
const FormObjectTextStyled = withStyles(styles)(FormObjectText);
and then access the classes prop inside FormObjectText.
Here's a sandbox showing both approaches:

React input onChange lag

I have a simple controlled input with an onChange event handler.
Everything works as it should with handleChange firing on every keystroke, the problem is it is very slow.
There is a very noticeable lag when using the input. Is there some extra code I have to right to get this to work like a normal input?
Do I have to debounce the input?
There is no mention of this issue in the docs as far as I can tell, and I don't know if there's something extra I have to do or if I'm using the onChange callback incorrectly.
handleChange = (event) => {
this.setState({ itemNumber: event.target.value })
}
<TextField
id="Part #"
label="Part #"
value={this.state.itemNumber}
onChange={this.handleChange}
margin="normal"
/>
The component:
export class Dashboard extends Component {
state = {
report: '',
selectedDate: new Date(),
itemNumber: '',
}
static propTypes = {
classes: object,
headerTitle: string,
userInfo: object,
}
static defaultProps = {
classes: {},
headerTitle: undefined,
userInfo: {},
}
reportSelected = (event) => {
this.setState(() => {
return {
report: event.target.value,
}
})
}
handleDateChange = (date) => {
this.setState({ selectedDate: new Date(date) })
}
handleChange = (event) => {
this.setState({ itemNumber: event.target.value })
}
render () {
const { classes, headerTitle, userInfo } = this.props
return (
<div className={classes.dashboard}>
<HeaderTitle title="Dashboard" />
<Helmet>
<title>{headerTitle}</title>
</Helmet>
{ userInfo.isAuthorized &&
<Grid container direction={'row'} justify={'center'} className={classes.formContainer}>
<Grid item xs={12} sm={12} md={12} lg={6} xl={5}>
<form className={classes.form}>
<FormControl className={classes.presetReportsInput}>
<InputLabel htmlFor="reports">Preset Reports</InputLabel>
<Select
value={this.state.report}
onChange={this.reportSelected}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{presetReports.getReportList().map(report => (
<MenuItem value={report.name} key={report.name}>
{report.name}
</MenuItem>
))}
</Select>
</FormControl>
{ (this.state.report === 'Inventory Snapshot' ||
this.state.report === 'Weekly Fill Rate' ||
this.state.report === 'Open Orders' ||
this.state.report === 'Weekly Shipments') &&
<div>
<Grid container spacing={8} direction={'row'}>
<Grid item>
<MuiPickersUtilsProvider utils={MomentUtils}>
<DatePicker
className={classes.datePicker}
margin="normal"
keyboard
format="DD/MM/YYYY"
disableFuture
autoOk
mask={value => (value ? [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/] : [])}
value={this.state.selectedDate}
onChange={this.handleDateChange}
disableOpenOnEnter
animateYearScrolling={false}
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item>
<TextField
id="Part #"
label="Part #"
value={this.state.itemNumber}
onChange={this.handleChange}
margin="normal"
/>
</Grid>
</Grid>
<Button variant="raised" color="primary" style={{ marginTop: 10 }}>
Search
</Button>
</div>
}
{ this.state.report === '' &&
<div>
<TextField
id="queryField"
label="Run a Query"
className={classes.queryField}
helperText=""
margin="normal"
multiline
rows="5"
/>
<Grid container direction={'row'} justify={'flex-end'}>
<Grid item>
<Button variant="raised" color="primary">
Export
</Button>
</Grid>
<Grid item>
<Button variant="raised" color="primary">
Save Query
</Button>
</Grid>
</Grid>
</div>
}
</form>
</Grid>
{ this.state.report === 'Inventory Snapshot' &&
<Grid container className={classes.table}>
<Grid item xs={12} sm={12} md={12} lg={12} xl={12}>
<InventoryReport />
</Grid>
</Grid>
}
</Grid>
}
</div>
)
}
}
const styles = {
dashboard: {},
formContainer: {
margin: 0,
width: '100%',
},
presetReportsInput: {
width: '100%',
margin: '20% 0 0 0',
},
queryField: {
width: '100%',
margin: '20% 0 0 0',
},
table: {
margin: '50px 0 10px 0',
},
datePicker: {
marginTop: 32,
},
}
const mapStateToProps = state => {
const { layout } = state
const { headerTitle } = layout
return {
headerTitle: headerTitle,
}
}
export default connect(mapStateToProps)(withStyles(styles)(Dashboard))
I'm watching the state update in react devtools in chrome and there's at least a 500ms lag between a character being entered and the state updating, much longer for faster typing. Why is setState so slow? What's the workaround to getting this form to behave like a normal web form?
setState by itself is not slow, it is only when your renders get very expensive that it starts causing issues.
A few things to consider are:
Your main component seems quite large and is being re-rendered on every keystroke so that might cause performance issues. Try breaking it down to smaller components.
Ensure the child-components being rendered in the render method of your main component do not get unnecessarily re-rendered. React Developer Tools or why-did-you-render can point out those unnecessary rerenders. Switching to PureComponent, stateless components or using shouldComponentUpdate can help.
While you can't avoid rerendering here (since your form inputs need to rerender with the new state values), by breaking into smaller components you can look to use shouldComponentUpdate to let React know if a component’s output is not affected by the current change in state or props and avoid unnecessarily rerendering.
If you use functional components:
Use useMemo to prevent recomputing expensive operations or components unless some dependency changed.
Use useCallback to return a memoized version of a callback so that child components that rely on reference equality don't unnecessarily rerender
Use React.memo if your functional component renders the same result given the same props to prevent it from unnecessarily rerendering. Use the second argument to React.memo to customize the behaviour of the memoization (similar to shouldComponentUpdate)
Switch to the production build to get better performance
Switch to uncontrolled components and let the DOM handle the input components itself (this is the "normal web form" behaviour you described). When you need to access the form's values you can use ref's to get access to the underlying DOM nodes and read the values directly off that. This should eliminate the need to call setState and therefore rerender
This is especially an issue for big forms when a change in one input triggers the whole component re-render and that too in redux, although redux does not seem to be interfering here.
If you want your inputs to be controlled and have the same exact behaviour then you can always do like this
<input
className="form-control"
type="text"
name="name"
value={form.name}
onBlur={onChangeHandler}
/>
This only triggers an event on blur and prevent re-render on each change. It's useful since when you click on any other button to process the data, it's guaranteed that you'll have the updated state.
This will not be helpful if your logic requires instant validation/processing related to the input data.
Also, It's important I should mention that sometimes this fails to work with other components which are not native to HTML5 since they might prevent a re-render based on the value prop
Note: Please read the onBlur event here
This might seem like a trivial response but — make sure your console is closed. There is a noticeable lag in controlled components when the console is open!
if you're using a big parent component with too many re-render dependencies
I recommend you to handle re-renders in useEffect of child components
or if its force to use all state updates in the parent
use debounce in the child components.
Options given by #ᴘᴀɴᴀʏɪᴏᴛɪs are good but for my use case it didn't help. I solved it by adding a debounce before setting the state.
const delaySetStateWithDelay = () => {
if (lastRequest) {
window.clearTimeout(lastRequest);
}
lastRequest = setTimeout(() => {
setStateData('Data I Need To Set'
}, 500);
};
You could use https://reactjs.org/docs/perf.html to profile your app. Do you have a large number of components which could be getting re-rendered? It might be necessary to add some componentShouldUpdate() methods to your components to prevent useless re-renders.
I recently faced the same problem, I had a redux store and a description in it, after every keystroke in the description input the store had to be updated, I tried to debounce in lodash but it didn't work, so I created a set timeout function to simply update the store state like this,
setTimeout(() => {
console.log("setting");
this.props.addDescription(this.state.description);
}, 200);
I take the description input field value from the description components' own state and whenever it re-renders I use componentDidMount() to get the latest updated description value from the store.
I had similar problem with slow input in development mode, but with functional components and hooks. Production was ok, but obviously switching on productions doesn't look like approach, if it so slow in development mode there is high chance some problem with code exists. So solution was to isolate state of input from the rest components. State that used by component should be available only for this component. Actually it's not even a solution, but how things should be in react.
I was having the same problem a while ago, had to copy the state coming from a parent component to a local object and then reference the new local object to my input.
If you don't need to use the new state anywhere else prior to saving the form, I reckon you can use the solution below.
const { selectedCustomer } = props;
const [localCustomer, setLocalCustomer] = useState({ name: 'Default' });
useEffect(() => {
setLocalCustomer({ ...selectedCustomer });
}, [selectedCustomer]);
const handleChangeName = (e) => {
setLocalCustomer({ ...localCustomer, name: e.target.value });
};
and then use it in my text field.
<StyledTextField
fullWidth
type='text'
label='Name'
value={localCustomer.name}
</StyledTextField>
Just adding a quick setTimeout was enough to improve performance a lot. The concern I had about timeouts of 100ms or longer was that if submit was executed, depending on the implementation, setState data may not have been added yet.
Something like below should prevent this happening in any real situation:
const onChange = (mydata) => setTimeout(() => setState(mydata), 10);
as noted in #Sudhanshu Kumar's post above, you can use 'onBlur' to execute the setState call when the input element loses focus by passing onChange handler as a callback. To get this to work with MUI use the following...
<TextField
...props
inputProps={{
onBlur: handleChange
}}
/>
This allows for override of native browser onBlur method. Hope this helps.

Resources