I am using React Material UI for creating Multiple select and I use react-hook-form
I dont know why i Didnt get the value of what i selected on select menu, i console the `console.log('field', field)` and I received this data
{name: 'location', value: '', onChange: ƒ, onBlur: ƒ, ref: ƒ}
this is the value of event.target.value if I select 2 names
0: "Oliver Hansen"
1: "Van Henry"
length:
2
[[Prototype]]: Array(0)
and if I select 1 name only
0: "Oliver Hansen"
length:
1
[[Prototype]]: Array(0)
my current code
const name= [
'Oliver Hansen',
'Van Henry',
];
const [getNewName, setNewName] = useState([])
const handleChange = (event) => {
setNewName(event.target.value);
};
....,
<Controller
name="username"
control={control}
defaultValue={mode === 'edit' ? (selectedAccount.Name) : ''}
render={({ field, fieldState: { error } }) => (
<>
<FormControl variant="outlined" className="form-text" sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Please Select the USER assigned to this server</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
variant="outlined"
value={getNewName}
{...field}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => (<Chip key={value} label={value}/>))}
</Box>
)}
MenuProps={MenuProps}
>
{name&& name.map((name) => (
<MenuItem
key={name}
value={name}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
{console.log('field: ', field)}
</>
)}>
Related
I have two autocomplete components in my form where one depends on the other. I want that at a choice of a field in the first component values in the second were reset. I tried a bunch of ways to do it, but nothing worked. Please, tell me how can I solve this problem?
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmitAppealForm)}>
<Grid item container xs={12} sx={{ mt: 2 }} columnSpacing={3}>
<Grid item xs={6}>
<SelectControl
required
control={control}
options={[
{ id: 1, name: "1 - Тематика" },
{ id: 2, name: "2 - Категория" },
{ id: 3, name: "3 - Подкатегория" },
]}
placeholder="Уровень тематики"
label="Уровень тематики *"
name="themeLevel"
readonly
/>
</Grid>
<Grid item xs={6}>
<SelectControl
control={control}
options={getProjectThemesArray() ?? []}
disabled={!themeLevel || themeLevel === 1}
label="Родительская тематика"
defaultValue=""
name="parentThemeId"
loading={isParentThemeFetching}
/>
</Grid>
</Grid>
</form>
</FormProvider>
Autocomplete component:
return (
<>
<Controller
name={name}
control={control}
render={({ field: { value, onChange: handleChange } }) => (
<>
<div className="form-label">{label}</div>
<Autocomplete
onChange={(e, value) => {
handleChange(e, value);
onChange && onChange();
}}
options={options}
noOptionsText="Нет данных"
clearText=""
closeText=""
openText=""
autoHighlight
disabled={loading || disabled}
renderInput={(params) => (
<OutlinedInput
name={name}
{...params.InputProps}
inputProps={{
...params.inputProps,
}}
value={options.length > 1 ? value?.name : ""}
readOnly={readonly}
className={input}
required={required}
placeholder={
defaultValue && disabled
? defaultValue
: placeholder ?? label
}
fullWidth
/>
)}
isOptionEqualToValue={(option, value) => option.id === value.id}
getOptionLabel={(option) => option.name || ""}
renderOption={(props, option) => (
<MenuItem {...props} key={option.id} value={option.id}>
{option.name}
</MenuItem>
)}
popupIcon={
<div className={icon}>
{loading ? <Spinner width={20} height={20} /> : <Arrow />}
</div>
}
/>
</>
)}
/>
</>
I used reset and setField from useForm to my component but it didn't work. Also, resetting the state of the input did not work either.
I'm using Material UI to create an application with React. I have a Dialog that allows the user to change a series of information about an object (see the code below). There are two different buttons. Each button refers to a different object. So, depending on which object the user wants to change, the onDialogOpen function is called with different parameters. Everything is working fine.
export default function CollectingFormInput() {
const [dialogOpen, setDialogOpen] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [name, setName] = useState("");
const [alias, setAlias] = useState("");
const [latitude, setLatitude] = useState("");
const [longitude, setLongitude] = useState("");
const [address, setAddress] = useState("");
const [notes, setNotes] = useState("");
const onDialogOpen = (info) => {
setName(info.name);
setAlias(info.alias);
setLatitude(info.latitude);
setLongitude(info.longitude);
setAddress(info.address);
setNotes(info.notes);
setDialogOpen(true);
};
const onDialogClose = () => {
setDialogOpen(false);
};
const onSnackbarClose = (e, reason) => {
if (reason === 'clickaway') {
return;
}
setSnackbarOpen(false);
setSnackbarMessage('');
};
const onCreate = () => {
setSnackbarOpen(true);
setSnackbarMessage(`Dados de ${name} atualizados!`);
onDialogClose();
};
return (
<Fragment>
<Button color="primary" onClick={()=> onDialogOpen({
name: "JAF_01",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})}>
Botão 1
</Button>
<Button color="primary" onClick={()=> onDialogOpen({
name: "JAF_02",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})}>
Botão 2
</Button>
<Dialog open={dialogOpen} onClose={onDialogClose}>
<DialogTitle>Editar informações da estação</DialogTitle>
<DialogContent>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
margin="normal"
id="station-name"
id="outlined-read-only-input"
label="Nome"
defaultValue={name}
size="small" fullWidth
InputProps={{
readOnly: true,
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
margin="normal"
id="station-alias"
id="outlined-basic"
label="Apelido"
InputProps={{ name: 'alias' }}
onChange={field => setAlias(field.target.value)}
value={alias}
defaultValue=""
size="small" fullWidth
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
margin="normal"
id="station-latitude"
id="outlined-basic"
label="Latitude"
InputProps={{ name: 'latitude' }}
onChange={field => setLatitude(field.target.value)}
value={latitude}
defaultValue=""
size="small"
fullWidth
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
margin="normal"
id="station-longitude"
id="outlined-basic"
label="Longitude"
InputProps={{ name: 'longitude' }}
onChange={field => setLongitude(field.target.value)}
value={longitude}
defaultValue=""
size="small"
fullWidth
/>
</Grid>
</Grid>
<TextField
margin="normal"
id="station-address"
id="outlined-basic"
label="Endereço"
InputProps={{ name: 'address' }}
onChange={field => setAddress(field.target.value)}
value={address}
defaultValue=""
size="small"
fullWidth
/>
<TextField
margin="normal"
id="station-obs"
id="outlined-multiline-flexible"
multiline
maxRows={4}
label="Observações"
InputProps={{ name: 'notes' }}
onChange={field => setNotes(field.target.value)}
value={notes}
defaultValue=""
size="small"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={onDialogClose} color="primary">Cancelar</Button>
<Button
variant="contained"
onClick={onCreate}
color="primary"
>
Salvar
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={snackbarOpen}
message={snackbarMessage}
onClose={onSnackbarClose}
autoHideDuration={4000}
/>
</Fragment>
);
}
Now, I'd like to clean up the code and create a new CollectingFormInput component that is independent of the two buttons. So my code would be something like...
<Fragment>
<Button color="primary" onClick={()=> onDialogOpen({
name: "JAF_01",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})}>
Botão 1
</Button>
<Button color="primary" onClick={()=> onDialogOpen({
name: "JAF_02",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})}>
Botão 2
</Button>
<CollectingFormInput />
<Fragment>
I think that onDialogOpen would have to belong to the CollectingFormInput component and be called by the parent component.
I searched but couldn't find a way to access onDialogOpen from the parent component. Can someone help me?
You can use useImperativeHanlde hook and forwardRef hook to expose some methods in a child component to a parent component.
Wrap the CollectingFormInput in a forwardRef. And use useImperativeHanlde hook to expose methods in it as below.
import { forwardRef, useImperativeHandle } from "react";
const CollectingFormInput = forwardRef((props, ref) => {
...
...
...
const onDialogOpen = (info) => {
setName(info.name);
setAlias(info.alias);
setLatitude(info.latitude);
setLongitude(info.longitude);
setAddress(info.address);
setNotes(info.notes);
setDialogOpen(true);
};
const onDialogClose = () => {
setDialogOpen(false);
};
useImperativeHandle(ref, () => ({
onDialogOpen: onDialogOpen,
onDialogClose: onDialogClose
}));
...
...
return (
...
...
);
});
In the parent component create a ref and pass it to CollectingFormInput to get initialized with the functions you exposed there in the component. Call the methods after doing the null / undefined checking.
import { useRef } from "react";
const ParentComp = () => {
const dialogHandleRef = useRef(null);
return (
<Fragment>
<Button
color="primary"
onClick={() =>
dialogHandleRef &&
dialogHandleRef?.current?.onDialogOpen({
name: "JAF_01",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})
}
>
Botão 1
</Button>
<Button
color="primary"
onClick={() =>
dialogHandleRef &&
dialogHandleRef?.current?.onDialogOpen({
name: "JAF_02",
alias: "",
latitude: 0,
longitude: 0,
address: "",
notes: ""
})
}
>
Botão 2
</Button>
<CollectingFormInput ref={dialogHandleRef} />
</Fragment>
);
};
I am building a category adder and previously I was only allowing the "add category chip" to show for the nooptions render, but I realized that if there was a category "software ngineer" and someone wanted to add "Software" you couldn't do it.
Ultimately I want to add the Add chip (which is different then the normal chips) to the end of all of the options with the search term in it. I tried adding it to the render option, but then it showed up after every option and I tried adding it to filter options but then I can't customize that chip.
Any suggestions on solving this?
My code:
<Autocomplete
id="Categories"
// set it to the left of the cell
style={{ width: '100%', marginTop: '1rem' }}
key={autocompleteKey}
options={
user.categories.length > 0
? user.categories.filter((c) => {
return !category.includes(c || c.charAt(0).toUpperCase() + c.slice(1));
})
: []
}
onChange={(event, value) => (handleAddCategoryToCompany(value))}
onInputChange={(event, value) => setNewCategory(value)}
popupIcon=""
filterOptions={(options) => {
console.log(options);
const result = [...options];
if (search.length > 0) result.push(search);
return result;
}}
renderOption={(event, value: string) => (
<>
<Chip
variant="outlined"
sx={{
m: '2px',
}}
onClick={() => handleAddCategoryToCompany(value)}
key={Math.random()}
label={value.charAt(0).toUpperCase() + value.slice(1)}
onDelete={() => removeFromGlobal(value)}
/>
{/* {search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null} */}
</>
)}
renderInput={(par) => (
// Textfield where the options have a delete icon
<TextField
{...par}
fullWidth
label="Add Tag(s)"
margin="normal"
size="small"
name="Name"
onChange={(event) => (setSearch(event.target.value))}
key={autocompleteKey}
type="company"
value={search}
variant="outlined"
/>
)}
noOptionsText={search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null}
/>
I figured it out by adding a modifier to the search option in the filter options and then had a conditional statement in render to look for this option. Seems to be working.
<Autocomplete
id="Categories"
// set it to the left of the cell
style={{ width: '100%', marginTop: '1rem' }}
key={autocompleteKey}
options={
user.categories.length > 0
? user.categories.filter((c) => {
return !category.includes(c || c.charAt(0).toUpperCase() + c.slice(1));
})
: []
}
onChange={(event, value) => (handleAddCategoryToCompany(value))}
onInputChange={(event, value) => setNewCategory(value)}
popupIcon=""
filterOptions={(options) => {
console.log(options);
const result = [...options];
if (search.length > 0) result.push(`${search} (new)`);
return result;
}}
renderOption={(event, value: string) => (
<>
{value.indexOf(' (new)') > -1
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
)
: (
<Chip
variant="outlined"
sx={{
m: '2px',
}}
onClick={() => handleAddCategoryToCompany(value)}
key={Math.random()}
label={value.charAt(0).toUpperCase() + value.slice(1)}
onDelete={() => removeFromGlobal(value)}
/>
)}
</>
)}
renderInput={(par) => (
// Textfield where the options have a delete icon
<TextField
{...par}
fullWidth
label="Add Tag(s)"
margin="normal"
size="small"
name="Name"
onChange={(event) => (setSearch(event.target.value))}
key={autocompleteKey}
type="company"
value={search}
variant="outlined"
/>
)}
noOptionsText={search.length > 0
? (
<Chip
variant="outlined"
onClick={handleAddCategory}
key={Math.random()}
sx={{
m: '2px',
}}
label={search}
icon={<AddCircleIcon />}
/>
) : null}
/>
I have the following component for selecting roles:
export const MultipleSelectChip = ({
options,
label,
error,
onRolesUpdate,
}: Props) => {
const theme = useTheme();
const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
const handleChipChange = (
event: SelectChangeEvent<typeof selectedOptions>,
) => {
const {
target: { value },
} = event;
setSelectedOptions(
// On autofill we get a the stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="multiple-chip-label">{label}</InputLabel>
<Select
required
labelId="multiple-chip-label"
error={error}
id="demo-multiple-chip"
multiple
value={selectedOptions}
onChange={handleChipChange}
input={<OutlinedInput id="select-multiple-chip" label={label} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{options.map((propOption) => (
<MenuItem
key={propOption.id}
value={propOption.name}
style={getStyles(propOption, selectedOptions, theme)}
>
{propOption.name}
</MenuItem>
))}
</Select>
<FormHelperText>Here's my helper text</FormHelperText>
</FormControl>
</div>
);
};
For options I have an array of objects with id and name, the thing is that I want to use the names for displaying the chips and the ids to pass them to the parent component for the add request. I don't know how to get de ids, too.
This is the example: https://codesandbox.io/s/6ry5y?file=/demo.tsx but is using an array of strings instead of an array of objects.
This is how 'options' looks like:
const rolesDummy: Role[] = [
{ id: '61fb0f25-34aa-46c6-8683-093254223dcd', name: 'HR' },
{ id: '949b9b1e-d3f8-45cb-a061-08da483bd486', name: 'Interviewer' },
{ id: 'c09ae2d4-1335-4ef0-8d4b-ee9529796b52', name: 'Hiring Manager' },
];
And I need to get back only the selected ids
Thank you!
If you pass the option as an object, you can render each MenuItem with the option.id as a key and the option.name as the label. The MenuItem is identified by an id:
<Select {...}>
{options.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
To display the name in the selected Chip. Use renderValue, but it only provides you the selected values (array of option.id), so you need to find the option to get the name:
renderValue={(selected) => {
return (
<Box>
{selected.map((value) => {
const option = options.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
Now you can get an array of selected ids by adding a change handler:
onChange={e => console.log(e.target.value)}
I need to disable next button until the form is filled out by the user. Full component is attached in here. I need to disable the next button. This form is build useFrom() in react-hook-from with material UI. This is integrated with API data for the address1, city, and the zip fields. Next button is process to the Payment gateway. So I need to disable the button until the form fields are completed. Only need to validate is all the fields are filled and once completed next is show to click.
const AddressForm = ({ checkoutToken, test }) => {
const [shippingCountries, setShippingCountries] = useState([]);
const [shippingCountry, setShippingCountry] = useState('');
const [shippingSubdivisions, setShippingSubdivisions] = useState([]);
const [shippingSubdivision, setShippingSubdivision] = useState('');
const [shippingOptions, setShippingOptions] = useState([]);
const [shippingOption, setShippingOption] = useState('');
const methods = useForm();
const fetchShippingCountries = async (checkoutTokenId) => {
const { countries } = await commerce.services.localeListShippingCountries(checkoutTokenId);
setShippingCountries(countries);
setShippingCountry(Object.keys(countries)[0]);
};
const fetchSubdivisions = async (countryCode) => {
const { subdivisions } = await commerce.services.localeListSubdivisions(countryCode);
setShippingSubdivisions(subdivisions);
setShippingSubdivision(Object.keys(subdivisions)[0]);
};
const fetchShippingOptions = async (checkoutTokenId, country, stateProvince = null) => {
const options = await commerce.checkout.getShippingOptions(checkoutTokenId, { country, region: stateProvince });
setShippingOptions(options);
setShippingOption(options[0].id);
};
useEffect(() => {
fetchShippingCountries(checkoutToken.id);
}, []);
useEffect(() => {
if (shippingCountry) fetchSubdivisions(shippingCountry);
}, [shippingCountry]);
useEffect(() => {
if (shippingSubdivision) fetchShippingOptions(checkoutToken.id, shippingCountry, shippingSubdivision);
}, [shippingSubdivision]);
return (
<>
<Typography variant="h6" gutterBottom>Shipping address</Typography>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit((data) => test({ ...data, shippingCountry, shippingSubdivision, shippingOption }))}>
<Grid container spacing={3}>
<FormInput required name="firstName" label="First name" />
<FormInput required name="lastName" label="Last name" />
<FormInput required name="address1" label="Address line 1" />
<FormInput required name="email" label="Email" />
<FormInput required name="city" label="City" />
<FormInput required name="zip" label="Zip / Postal code" />
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Country</InputLabel>
<Select value={shippingCountry} fullWidth onChange={(e) => setShippingCountry(e.target.value)}>
{Object.entries(shippingCountries).map(([code, name]) => ({ id: code, label: name })).map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Subdivision</InputLabel>
<Select value={shippingSubdivision} fullWidth onChange={(e) => setShippingSubdivision(e.target.value)}>
{Object.entries(shippingSubdivisions).map(([code, name]) => ({ id: code, label: name })).map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Options</InputLabel>
<Select value={shippingOption} fullWidth onChange={(e) => setShippingOption(e.target.value)}>
{shippingOptions.map((sO) => ({ id: sO.id, label: `${sO.description} - (${sO.price.formatted_with_symbol})` })).map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
</Grid>
<br />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button component={Link} variant="contained" to="/cart" color="secondary">Back to Cart</Button>
<Button type="submit" variant="contained" color="primary">Next</Button>
</div>
</form>
</FormProvider>
</>
);
};
You can use a valid function as mentioned before
const isValid = shippingCountries && shippingCountry && shippingSubdivisions && shippingSubdivision && shippingOptions && shippingOption
<Button disabled={!isValid} />
As you use FormProvider and useForm() i think you use react-hook-form? If yes it is even more easy because it has an integrated validator.
For example you could use it like that
const { register, handleSubmit, setValue } = useForm();
....
<form onSubmit={methods.handleSubmit((data) => test({ ...data, shippingCountry, shippingSubdivision, shippingOption }))}>
<FormInput {...register("firstName, {required: true})} />
</form
With setValue you can set the values of the form from external (for example your useEffect function)
setValue([{ firstName: name }, { secondOption: option }]);
reference code:
<Button type="submit" variant="contained" color="primary" disabled={!this.state.shippingCountry || !this.state.shippingSubdivision || ...}>Next
PS: you can have a function which return true or false for the above and then enable...