onChange useState one character behind - reactjs

I have a simple search component and I want to build an autocomplete filter, but my onChange event handler is one character behind.
If I type "tsla" into the search bar my value will be "tsl"
<TextField
className={classes.queryField}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SvgIcon fontSize="small" color="action">
<SearchIcon />
</SvgIcon>
</InputAdornment>
)
}}
onChange={event => {
setValue(event.target.value);
setAuto(
tickers
.filter(
f =>
JSON.stringify(f)
.toLowerCase()
.indexOf(value) !== -1
)
.slice(0, 10)
);
console.log(auto);
}}
value={value}
placeholder="Search for a stock"
variant="outlined"
/>

The issue is that you are sill using the old value variable when you call setAuto.
const Search = (props) => {
handleChange = (event) => {
const value = event.target.value
setValue(value);
setAuto(tickers.filter((ticker) => JSON.stringify(ticker).toLowerCase().indexOf(value) !== -1).slice(0, 10));
console.log(auto);
};
return (
<TextField
className={classes.queryField}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SvgIcon fontSize="small" color="action">
<SearchIcon />
</SvgIcon>
</InputAdornment>
),
}}
onChange={handleChange}
placeholder="Search for a stock"
value={value}
variant="outlined"
/>
);
};
The solution is to pass the new value object to both setValue and to setAuto.
handleChange = (event) => {
const value = event.target.value
setValue(value);
setAuto(tickers.filter((ticker) => JSON.stringify(ticker).toLowerCase().indexOf(value) !== -1).slice(0, 10));
console.log(auto);
};

Related

Material UI: How do I reset inputValue when using InputBase for Autocomplete?

The following will allow you to prepare your own behavior like the one in the title, but it is not possible to do it in the following way
However, since debounce() is called each time onInputChange is performed, if the key is pressed for a long time
This method is not realistic because the processing becomes heavy and the input value becomes choppy.
How can the inputBase value be reset in such a case?
const [q, setQ] = useState('');
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement>,
value: string
) => {
setQ(value);
debounce(value);
};
const handleClickClear = () => {
setQ('');
debounce('');
};
<Autocomplete
getOptionLabel={(option) =>
typeof option === "string" ? option : option.word
}
inputValue={q}
options={data}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<InputBase
inputProps={{
...params.inputProps,
name: "search",
type: "text",
}}
/>
{q && (
<ButtonBase onClick={handleClickClear}>
<IconButton size="small">
<ClearIcon fontSize="small" />
</IconButton>
</ButtonBase>
)}
</div>
)}
blurOnSelect
freeSolo
openOnFocus
onChange={handleChange}
onFocus={handleFocus}
onInputChange={handleInputChange}
/>;

React useRef not working with Material UI TextField

why is useRef not working with Material UI TextField but works fine with a traditional html input element?
The UI:
I have a Textfield input element and above it I have a list of buttons (letters from the latin alphabet, special characters). I can focus the input bar when the component renders the first time, but if I click on one of the buttons above the input, the focus is not back on the input bar.
What I tried so far
const Search = (props) => {
const [searchTerm, setSearchTerm] = useState("");
const inputRef = useRef(null);
const handleLatinButton = (letter) => {
inputRef.current.focus();
setSearchTerm(searchTerm + letter);
};
const handleSubmit = (e) => {
e.preventDefault();
History.push("/?q=" + searchTerm);
props.onFormSubmit(searchTerm, optionValue);
setSearchTerm("");
};
useEffect(() => {
const params = new URLSearchParams(location.search);
const q = params.get("q");
setSearchTerm(q);
inputRef.current.focus();
}, []); // Am I missing something here in the array?
JSX
<Button letter="č" handleLatinButton={handleLatinButton} />
<Button letter="ḍ" handleLatinButton={handleLatinButton} />
...
<form onSubmit={handleSubmit}>
<TextField
fullWidth
label="Search..."
value={searchTerm}
onChange={handleChange}
ref={inputRef}
autoFocus="true"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<IconButton type="submit">
<SearchIcon />
</IconButton>
</InputAdornment>
),
}}
/>
</form>
If I replace TextField with input, the focus works after I click on one of the latin buttons.
It's just the way MUI handles the ref...
<TextField
fullWidth
label="Search..."
value={searchTerm}
onChange={handleChange}
change this >>> ref={inputRef}
for this >>> inputRef={inputRef}
autoFocus="true"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<IconButton type="submit">
<SearchIcon />
</IconButton>
</InputAdornment>
),
}}
/>
perhaps to avoid confusion change the ref name

How to Increment and Decrement in ReactJS using Formik

I have a very simple problem incrementing and decrementing the value in my TextField.
I'm using Formik and Material UI in React.
Pls see this codesandbox link
CLICK HERE
<TableCell>
{" "}
<TextField
variant="outlined"
fullWidth
type="number"
name={`data.${idx}.returnValue`}
value={
values.data[idx] &&
values.data[idx].returnValue
}
onChange={handleChange}
onBlur={handleBlur}
inputProps={{
min: 0,
style: {
textAlign: "center"
}
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<IconButton>
<RemoveCircleOutlineIcon />
</IconButton>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton>
<AddCircleOutlineIcon />
</IconButton>
</InputAdornment>
)
}}
/>
</TableCell>
Use setValues to update your values object. Every time the user clicks the increment/decrement button you call setValues to update the current record.
This is what your RemoveCircleOutlineIcon onClick function would look like for returnValue's textfield.
...
onClick={() => {
// decrement current record's returnValue
const newData = values.data.map((currData) => {
if (currData.name !== record.name) return currData;
return { ...currData, returnValue: currData.returnValue - 1 };
});
setValues({ ...values, data: newData });
}}
...this one for AddCircleOutlineIcon, basically the same we're just incrementing the currrent record's returnValue.
onClick={() => {
// increment current record's returnValue
const newData = values.data.map((currData) => {
if (currData.name !== record.name) return currData;
return { ...currData, returnValue: currData.returnValue + 1 };
});
setValues({ ...values, data: newData });
}}
The onClicks for current record's dispatchValue textfield would be the same above except you'll update dispatchValue instead of returnValue. You get the idea.
You could use the replace function from arrayHelpers.
<IconButton
onPress={() => {
replace(idx, { ...record, returnValue: record.returnValue + 1})
}}
>
</IconButton>
You could check the working example here
You have to use States to render the changes, I modified your logic using useState. and added an event to change value each time you change press a button (+ or -).
const [products, setProduct] = useState(data);
const initialValues = {
customer_id: null,
reason: "",
products
};
// an example of the events
<InputAdornment position="start">
<IconButton
onClick={() => {
decrement(
idx,
"returnValue" // the key value
);
}}
>
<RemoveCircleOutlineIcon />
</IconButton>
</InputAdornment>
Check you code here to see it more clearly.

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>
)}
/>
</>

Any way to render Icon based on text field name using material UI?

<TextField
margin='dense'
fullWidth
value={this.props.value}
name={this.props.name}
type={this.props.type}
error={this.props.error !== ''}
helperText={this.props.error !== '' ? this.props.error : ' '}
onChange={ e => this.handleChange(e) }
label={this.props.label}
variant= {this.props.variant}
id={this.props.name}
InputProps={{
endAdornment: (
<AccountCircle />
),
}}
/>
Is there any way to display different Icons based on Text field name? I mean, If the name is Email then display EmailIcon. If profile then displays AccountCircle.
here is a simple solution so you can start it from here
let icon = null;
if (this.props.name === "Password") {
icon = <Visibility />;
} else if (this.props.name === "Account") {
icon = <AccountCircle />;
}
return (
<div className={classes.root}>
<TextField
label={this.props.name}
className={classNames(classes.margin, classes.textField)}
InputProps={{
endAdornment: icon
}}
/>
</div>
);
here I have put the name as a prop in this component and depend on that prop I change the icon. you can change this to switch if you wish.
hope you got an idea.
here is a link of a demo: https://codesandbox.io/s/moo68122lp
You can do it in an elegant way, by abstracting like this:
import { AccountCircle, UserIcon, PhoneIcon } from 'some/ui/library';
const icons = {
account: AccountCircle,
user: UserIcon,
phone: PhoneIcon,
};
const FieldIcon = ({ name }) => {
const Icon = icons[name];
return Icon ? (<Icon />) : null;
};
const YourComponent = props => (
<TextField
margin='dense'
fullWidth
value={props.value}
name={props.name}
type={props.type}
error={props.error !== ''}
helperText={props.error !== '' ? props.error : ' '}
label={props.label}
variant= {props.variant}
id={props.name}
InputProps={{
endAdornment: (
<FieldIcon name={props.name} />
),
}}
/>
);

Resources