React: MUI autocomplete with MUI form - javascript

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

Related

Cant Edit dynamic Textfield form graphql data in reactjs

I'm trying to create a dynamic textfield that takes data from gql like this
const { data } = useQuery(DATA_LIST, {
variables: {
param: {
limit: 10,
offset: 0,
sortBy: 'order'
}
}
});
const [state, setState] = useState<any>([]);
useEffect(() => {
if (data) {
setState(data?.dataList?.data);
}}, [data]);
then create a textField like this :
<TextField
name="name"
required
fullWidth
// label="Status Name"
onChange={(event) => handleChange(event, index)}
value={item?.name}
sx={{ marginRight: 5 }}
/>
<TextField
name="category"
required
fullWidth
select
// label="Category"
onChange={(event) => handleChange(event, index)}
value={item?.category}
>
{Category.map((option, index) => (
<MenuItem key={index} value={option.value}>
{option.name}
</MenuItem>
))}
</TextField>
handleChange :
const handleChangeInput = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const values = [...state];
values[index][event.target.name] = event.target.value;
console.log(values[index], 'ini values');
setState(values);
};
and call the inputRow component like this (im using drag and drop for textField list) :
{state.map((item: any, index: any) => {
// console.log(statusName[index]);
return (
<Draggable key={item.id} draggableId={String(item.id)} index={index}>
{(provided, snapshot): JSX.Element => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<Box marginRight={2}>
<TypographyComponent text={index + 1} type={'subBody'} />
</Box>
<InputRow index={index} item={item} handleChange={handleChangeInput} />
</div>
)}
</Draggable>
);
})}
but when i try to type the textfield, an error appears that Cannot assign to read only property
error message
This is weird because if I input dummy data, the textfield can be modified, but if I use data from the API the data cannot be modified.

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 Autocomplete: Add a value to startAdornment

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

get field value from Formik & Material UI form

I am trying to disable a checkbox group based on the value of a radio group. I followed the method used in the last part of the Formik tutorial. Using react context removes a lot of clutter from the form itself but I'm not sure how to expose some values now.
In the form below, in the CheckboxGroup component, I'm attempting to print the word disabled as an attribute of checkbox1 if radio4's value is "yes". I'm not sure what value should be used here as fields doesn't work. How do I pass a value to the form given the React Context method used?
The form:
export default function HealthAssessmentForm() {
return (
<Formik
initialValues={{
radio4: '',
symptoms: '',
}}
onSubmit={async (values) => {
await new Promise((r) => setTimeout(r, 500));
console.log(JSON.stringify(values, null, 2));
}}
validator={() => ({})}
>
<Form>
<RadioInputGroup
label="Disable the checkbox?"
name="radio4"
options={['Yes','No']}
/>
<CheckboxGroup
{(fields.radio4.value === "yes") ? "disabled" : null}
name="checkbox1"
options={[
{name:"hello",label:"hello"},
{name:"there",label:"there"},
]}
/>
<button type="submit">Submit</button>
</Form>
</Formik>
)
}
I'm not sure the custom components are relevant here but...
const RadioInputGroup = (props) => {
const [field, meta] = useField({...props, type:'radio'});
return (
<FormControl component="fieldset">
<FormLabel component="legend">{props.label}</FormLabel>
<RadioGroup aria-label={props.name} name={props.name} value={props.value}>
<FieldArray name="options">
{({ insert, remove, push }) => (
props.options.length > 0 && props.options.map((option,index) => (
<FormControlLabel key={index} {...props} value={option.toLowerCase()} control={<Radio />} label={option} />
))
)}
</FieldArray>
</RadioGroup>
</FormControl>
)
};
const CheckboxGroup = (props) => {
const [field, meta] = useField({...props, type: 'checkbox', });
return (
<FormControl component="fieldset">
<FormLabel component="legend">{props.label}</FormLabel>
<FormGroup>
<FieldArray name="options">
{({ insert, remove, push}) => (
props.options.length > 0 && props.options.map((option,index) => (
<FormControlLabel
{...field} {...props}
key={index}
control={<Checkbox />}
label={option.label}
/>
))
)}
</FieldArray>
</FormGroup>
<FormHelperText>Be careful</FormHelperText>
</FormControl>
)
}
I wrapped the whole <Form> in a function that passes props as an argument. I then get access to props.values.radio1. However, that has exposed that radio1 does not have a value even when it is clicked, which should be a separate issue.
{(props) => (
<Form>
<RadioInputGroup
label="Disable the checkbox?"
name="radio4"
options={['Yes','No']}
/>
<CheckboxGroup
disabled={props.values.radio1 === "No"}
name="checkbox1"
options={[
{name:"hello",label:"hello"},
{name:"there",label:"there"},
]}
/> </Form>
)}

Categories

Resources