Material-ui Autocomplete: Add a value to startAdornment - javascript

I have autocomplete with multiple selection permission.
https://codesandbox.io/s/bold-jackson-dkjmb?file=/src/App.js
In the example I have 3 options for cities. How can I manually add automatic added value in TextField when something is selected?
In other words here:
renderInput={(params) => {
console.log(params);
return (
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
);
}}
I want to be able to add to params.InputProps.startAdornment a value before rendering the textfield.
as every selected object seems to be very complex object, how I can do this manually(It is too complicated to push())? Any ideas how I can add object like this:
manually?

the value of startAdornment is undefined until a value is chosen from the dropdown/checkbox. So, you could add startAdornment property to the InputProps like below,
import { Chip } from '#material-ui/core';
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
chip: {
margin: theme.spacing(0.5, 0.25)
}
}));
const classes = useStyles();
const handleDelete = (item) => () => {...};
renderInput={(params) => {
console.log(params);
return (
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
startAdornment: (
<Chip
key={"manual"}
tabIndex={-1}
label={"manually added"}
className={classes.chip}
onDelete={handleDelete("blah")}
deleteIcon // requires onDelete function to work
/>
),
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
);
}}

The other solution didn't work 100% from myside,
As it adds the automatic field,
But new selected options -> are selected but chips not showing next to the automatic added option!!
So to fix this problem I made a few changes:
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
startAdornment: (
<>
<Chip
key={"manual"}
tabIndex={-1}
label={"manually added"}
className={classes.chip}
onDelete={handleDelete("blah")}
deleteIcon // requires onDelete function to work
/>
<React.Fragment> //the change
{" "}
{params.InputProps.startAdornment}{" "}. //the change
</React.Fragment>
</>
),
}}
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>

Related

React: MUI autocomplete with MUI form

I've been using a MUI form like this:
<Box component="form" onSubmit={event => {
return handleSubmit(event);
}} noValidate sx={{mt: 1}}>
<TextField
margin="normal"
required
fullWidth
id="title"
label="Titel"
name="title"
autoFocus
/>
<TextField
margin="normal"
required
multiline
rows={10}
fullWidth
label="Inhalt"
name="content"
id="content"
autoComplete="off"
/>
</Box>
This allowed me to extract the values from the MUI TextField components by using FormData:
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
let newsResponse = await createNews({
title: data.get('title'),
content: data.get('content'),
});
}
This works fine. Now I wanted to add a MUI Autocomplete component to the form:
<Autocomplete
multiple
id="tags"
className={props.className}
open={open}
isOptionEqualToValue={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={tags}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
required
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20}/> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
However, I found no way to access the value of said Autocomplete component. It does not even have a name attribute and adding a name attribute to the inner TextField component does not work either.
How can I access the value of it in manner like data.get('tags')?
Considering that both are MUI components I would expect them to have the same API.
The useState hook, something like this:
function MyForm() {
const [values, setValues] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(values);
};
return (
<form onSubmit={handleSubmit}>
<Autocomplete
multiple
id="tags"
className={props.className}
open={open}
isOptionEqualToValue={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={tags}
onChange={(event: any, newValues: string[] | null) => {
setValues(newValues || '');
}}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
required
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={20}/> : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
)}
/>
<button type="submit">Submit</button>
</form>
);
}

How to update a useState which has an array, inside this array I have objects this objects will update when the input value will change in react js

const [fvalues,setFvalues]=useState(data.map((ele,id)=>{
return(
{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}
);
}));
I want to update these objects when there is a change in input tag values
let handleChange = (e)=>{
let {name,value}=e.target;
data.map((ele,id)=>{
// return setFvalues({ ...fvalues[id], [name]: value });
setFvalues(fvalues[id].name)
})
// setFvalues(fvalues[0].name=value)
console.log(name,value);
}
but this logic is not working
I have mapped forms and want to submit all the forms with one submit button, I want to update the input values which is entered by the users
{datas.map((ele, id) => {
let val = id + 1;
return (
<>
<Box key={id}>
{/* <HealthAndContactPass key={id} fun={handelSubmit} psName={ele?.psName} address={ele?.address} /> */}
{/* <HealthAndContactPassForm errors={errors} handleSubmit={handleSubmit} register={register} id={id} psName={ele?.psName} address={ele?.address} onSubmit={onSubmit}/> */}
<Typography className={styles.psName}>{ele.psName}</Typography>
<Box className={styles.white_box}>
<Box className={styles.form_flex}>
<Box className={styles.mobile}>
<Select className={classes.select} name='countryCode' defaultValue={'+91'} value={code} {...register("code")}>
<MenuItem className={styles.code_id} value={'+91'}>+91</MenuItem>
<MenuItem className={styles.code_id} value={'+25'}>+25</MenuItem>
<MenuItem className={styles.code_id} value={'+12'}>+12</MenuItem>
<MenuItem className={styles.code_id} value={'+13'}>+13</MenuItem>
</Select>
<TextField helperText={ferrors?.mobile} value={fvalues[id].mobile} name="mobile" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Mobile Number" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].emailId} name="emailId" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Email Id" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].age} name="age" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="age" variant="outlined" onChange={handleChange} />
</Box>
</Box>
<Box className={styles.form_flex2}>
<TextField value={fvalues[id].destinationAddress} name="destinationAddress" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationAddress" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].destinationPin} name="destinationPin" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationPin" variant="outlined" onChange={handleChange} />
</Box>
<Box className={styles.hr}></Box>
{id===0?(
<Box className={styles.addres}>
<ThemeProvider theme={theme}>
<Checkbox className={classes.check} {...label} />
</ThemeProvider>
<Typography className={styles.selectAdd}>Select same address for all</Typography>
</Box>):null
}
</Box>
</Box>
</>
)
})}
In this case you don't "update" the state array per se, rather you create a clone of the state array then modify the values you want and set the state to be this cloned array. I wasn't quite sure what exactly you wanted to do to the array, but see the general example below:
const [state, setState] = useState([{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}]);
let handleChange = e => {
const {name, value} = e.target;
const stateClone = state.map((item, i) => ({...state[i], [name]: value }))
// do what you want to this new array
setState(stateClone); // update the state array with the new values
}
If you want to update one column of a row, you can create a callback that will take an index (that you'll get when you will render the array of rows) and that will return a callback that will take the event (triggered when the event is dispatched by the browser) and that will update your value.
Then, you only need to trigger a new state change by cloning the old array (using Array.prototype.map) and mapping the new value of the row at any given index. If the index does not match, this means that the row that is mapped is not concerned by the change event so we return the row as-is.
import React, {useState, useCallback} from "react";
const App = () => {
const [rows, setRows] = useState([
{id: 1, value: ""},
{id: 2, value: ""},
{id: 3, value: ""}
]);
const handleRowValueChange = useCallback(index => ({currentTarget: {value}}) => {
setRows(rows.map((row, rowIndex) => {
if (rowIndex === index) {
return {
...row,
value
};
}
return row;
}));
}, [rows]);
return (
<table>
<tbody>
{rows.map((row, index) => (
<tr key={row.id}>
<td>
<input value={row.value} onChange={handleRowValueChange(index)} />
</td>
</tr>
))}
</tbody>
</table>
);
};
export default App;
Update your handleChange function definition to
let handleChange = (e)=>{
let {name,value}=e.target;
const newData = data.map((ele,id)=>{
return { ...fvalues[id], [name]: value };
// setFvalues(fvalues[id].name)
})
setFvalues(newData);
console.log(name,value);
}
Here, you will first create a new array (newData) using data.map and then assign the same as the new state using the setFvalues call.
Currently, you are calling setFvalues inside data.map because of which the state is being updated again and again with an individual array element (an object, in your case) on each iteration of the map method.

I have an object. I need to print its name as <option> on an <selector> but return another variable

I have an object:
company: {
name: 'Google',
id: '123asd890jio345mcn',
}
I need to print its name as an option of material-ui selector (Autocomplete rendering TextField)
But when user clicks on it, i need to return it's ID to an State or something else, so I can use it later.
return (
<Autocomplete
id="company"
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
loadingText="Buscando empresas..."
isOptionEqualToValue={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.id}
renderOption={(props, option) => (
<Box component="li" {...props}>
{option.name}
</Box>
)}
options={options}
loading={loading}
noOptionsText="Nenhuma empresa cadastrada"
defaultValue={data.company}
renderInput={(params) => (
<TextField
{...register("company")}
{...params}
label="Empresa"
placeholder="Selecione a empresa"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
With that configuration, when user clicks on any option it retrieves it's ID as value, but I want to show the name on the input instead.
I've tried a lot. But the only argument I can retrieve from this Autocomplete is it's current value, which is ID OR name. I can't show the name and retrieve the ID.
Is it possible?
Have you tried using onChange? You can get the entire object from onChange second argument, value. I've made a simplified version here: https://codesandbox.io/s/react-repl-forked-nqv8pw?file=/src/index.js:464-504
<Autocomplete
// your other props
getOptionLabel={(option) => option.name}
onChange={(_, value) => console.log(value)}
// your other props
/>

Material-UI TextField loses focus on every onChange

I am creating the following component:
It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.
My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.
This is my component :
const MedicineSelect = ({ medications, setMedications, ...props }) => {
const { medicines } = useMedicines()
const classes = useStyles()
const handleChange = (index, target) => {
// setAge(event.target.value)
const newMedications = cloneDeep(medications)
newMedications[index][target.name] = target.value
setMedications(newMedications)
}
const handleAddMedicine = () => {
const newMedications = cloneDeep(medications)
newMedications.push({ medicine: '', dosis: '', time: '' })
setMedications(newMedications)
}
const handleDeleteMedicine = (index) => {
console.log('DELETE: ', index)
const newMedications = cloneDeep(medications)
newMedications.splice(index, 1)
setMedications(newMedications)
}
return (
<Paper style={{ padding: 5 }}>
<List>
{medications.map((medication, index) => (
<ListItem key={nanoid()} divider alignItems='center'>
<ListItemIcon>
<Tooltip title='Eliminar'>
<IconButton
className={classes.iconButton}
onClick={() => handleDeleteMedicine(index)}
>
<HighlightOffOutlinedIcon />
</IconButton>
</Tooltip>
</ListItemIcon>
<FormControl className={classes.formControl}>
<InputLabel
id={`${index}-select-${medication}-label`}
>
Medicamento
</InputLabel>
<Select
labelId={`${index}-select-${medication}-label`}
id={`${index}-select-${medication}`}
name='medicine'
value={medication.medicine}
onChange={(event) =>
handleChange(index, event.target)
}
>
{medicines.map((medicine) => (
<MenuItem
key={nanoid()}
value={medicine.name}
>
{medicine.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
// fullWidth
id={`${index}-text-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
</ListItem>
))}
<Button onClick={handleAddMedicine}>+ agregar</Button>
</List>
</Paper>
)
}
And here is where I set the component:
const [medications, setMedications] = useState([
{ medicine: '', dosis: '', time: '' },
])
...
<Grid item md={12} xs={12}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography variant='h4'>
Tratamiento:
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container disableGutters>
<MedicineSelect
medications={medications}
setMedications={setMedications}
/>
</Container>
</AccordionDetails>
</Accordion>
</Grid>
...
Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.
Please help me getting this fixed!!!
After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.
What I did was keeping the keys unmuttable:
<ListItem key={`medication-${index}`} divider alignItems='center'>
and
<TextField
key={`dosis-${index}`}
fullWidth
// id={`${index}-dosis-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>

How to get Material-UI AutoComplete renderOption to work for selected value?

Material-UI Autocomplete component let's you use an array of options and transform the options like so:
You have an array of option objects: [{option}, {option}.....{option}]
I use the prop function renderOption={option => option.title} to have the list of options be the options' titles, and I want to use the options' ids as the actual value to pass to the form handler so I use the built in function getOptionLabel={option => `${option.id}`}. This works until I select an option. The text in the input shows the id, not the title. How do I get the input's label to be the option's title, and still pass the id as the value?
The field
```
<Field
classes={classes}
name={field}
label={titleCase(field)}
InputLabelProps={{
classes: {
root: classes.label,
shrink: 'shrink'
}
}}
component={renderAutoCompleteDataField}
renderOption={(option )=> option[fieldProp]}
getOptionLabel={(option => `${option.id}`)}
options={choices}
/>
The renderAutoCompletefunction
export const renderAutoCompleteDataField = ({ options, nonfilter, renderOption, getOptionSelected, getOptionLabel, classes, input, onChange, label, fullWidth, value, defaultValue, ...custom }) => {
return (
<Autocomplete
options={options}
getOptionLabel={getOptionLabel}
renderOption= {renderOption}
getOptionSelected={getOptionSelected}
onSelect={onChange}
classes={{
listbox: classes.listbox,
input: classes.input,
option: classes.option
}}
renderInput={(params) => (
<TextField
{...params}
label={label}
size="small"
color="secondary"
variant="outlined"
fullWidth={fullWidth}
InputProps={{
...params.InputProps,
className: classes.listbox,
input: classes.input
}}
InputLabelProps={{ className: classes.label }}
{...input}
{...custom}
/>
)}/>
)
}

Categories

Resources