REACT 17 wrap mui tab in form how to control form submission with react hook form - material-ui

I'm stuck can a charitable soul advise me : I have a form that wraps 3 tabs
which contain text fields
interface TabPanelProps {
// eslint-disable-next-line react/require-default-props
children?: ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
aria-labelledby={`simple-tab-${index}`}
hidden={value !== index}
id={`simple-tabpanel-${index}`}
role="tabpanel"
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<div>{children}</div>
</Box>
)}
</div>
);
}
function getTab(index: number) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
export default function tt({ closeModal, testData }: any) {
const [value, setvalue] = useState(0);
const methods = useForm<MyInterface>();
const {
register,
handleSubmit,
formState: { errors, isDirty },
} = methods;
const onSubmit = (formValue: MyInterface) => {
console.log("isSubmitted");
console.log(formValue);
};
const handleChangeTab = (event: SyntheticEvent, newValue: number) => {
setvalue(newValue);
};
return (
<FormProvider {...methods} >
< form className="myForm"
onSubmit={methods.handleSubmit(onSubmit)} >
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs aria-label="basic tabs example" onChange={handleChangeTab} value={value}>
<Tab label="1" {...getTab(0)} />
<Tab label="2" {...getTab(1)} />
<Tab label="3" {...getTab(2)} />
</Tabs>
</Box>
<TabPanel index={0} value={value}>
<Component1 />
</TabPanel>
<TabPanel index={1} value={value}>
<Component2 testData={testData} />
</TabPanel>
<TabPanel index={2} value={value}>
<Component3 />
</TabPanel>
</Box>
<DialogActions>
<Button onClick={methods.handleSubmit(onSubmit)}>Enregistrer</Button>
<Button onClick={closeModal}>Annuler</Button>
</DialogActions>
</form>
</FormProvider>
);
}
2 and 3 have for example juste textField which are required in Component 1 but not in Component 2 and 3 :
import { useFormContext } from "react-hook-form";
import { TextField } from '#mui/material';
export default function Component1() {
const { register, formState } = useFormContext();
return (
<>
<TextField
error={Boolean(formState.errors?.name)}
fullWidth
helperText={Boolean(formState.errors?.name) === false ? "give the name" : formState.errors.name?.message}
{...register('name', { required: "name is required." })}
label="name"
variant="standard"
/>
</>
);
}
code in the Component2:
import { Controller, useFormContext } from 'react-hook-form';
import { Checkbox, Chip, FormControl, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent, TextField } from '#mui/material';
export default function Component3(props: any) {
const { register, control } = useFormContext();
return (
<>
<FormControl sx={{ m: 1, minWidth: '100%' }} variant="standard">
<InputLabel id="test">En prod</InputLabel>
<Controller
control={control}
defaultValue=""
name="test"
render={({ field }) => (
<Select labelId="test" {...field}>
<MenuItem value="yes">yes</MenuItem>
<MenuItem value="no">no</MenuItem>
</Select>
)}
/>
</FormControl>
my problem is:
when I change tab without clicking in a field of the form, that is sent even if the required fields are not filled.
On the contrary when I click in any field and the required fields are not filled in isDirty remains true.
Which is normal.
But if I fill in the required fields and I change tab isDirty always stays true and I can't send the form.
Thank you for your help!!

Related

mui Select/chip with Reach-hook-form - Can't get the update value (Reactjs)

I've succesfully implemented two seperate reusable MUI TextField and Select components , but having issue with third one, which contains both Mui Select/Chip in one single component, the code is two parts, one is the main component which call the second one,
/// Main component///
const { handleSubmit, reset, formState: { errors }, control } = useForm({
defaultValues: {
contractCode: 'sss', stores: [],
},
resolver: yupResolver(schema)
});
return (
.......
<Box m="1rem 0.7rem"
<FormInputText errors={errors} control={control} name='contractCode' label='Contract Code' />
<FormMultipleSelectChip errors={errors} control={control} name='stores' required label='Stores' />
</Box>
.......
);
/// Below is my Child component to re-use
const names = [
'Oliver Hansen',
'Virginia Andrews',
'Kelly Snyder',
];
export default function MultipleSelectChip({ label, inputProps, control, name, errors,stores }) {
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value },
} = event;
setPersonName(
};
return (
<div>
<FormControl >
<Typography> Locations </Typography>
<Controller
name={name}
control={control}
render={({ field : { onChange, value, ...rest} }) => (
<Select
{...rest}
multiple
value={personName}
onChange={handleChange}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem key={name} value={name} >
{name}
</MenuItem>
))}
</Select>
)}
/>
</FormControl>
</div>
);
}
Once I submitted the the form, Im not getting the value for 'stores' as attached, but im getting proper value for rest of the fields, enter image description hereenter image description here
Appreciate if anyone help me to fix this issue
Thanks
Syed

MUI dropdown react application

I am using MUI to develop my dashboard.
I am using MUI dropdown component to select the vendor details and there are two options for the Vendor dropdown. i.e "Surya Kumar Yadav", "Eswar". I used the same dropdown in all my cards. When I select the one of the dropdown in any card, it is reflected in all the other cards.
import React from "react";
import {
Box,
Card,
CardContent,
Typography,
Button,
CardActionArea,
CardActions,
Grid,
Select,
MenuItem,
FormControl,
InputLabel,
} from "#mui/material";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { HttpClient } from "../http/http";
import { ConfirmDialog, confirmDialog } from "./ConfirmDialog";
const Services = () => {
const [services, setServices] = useState([]);
const [vendors, setVednors] = useState([]);
const [selectedVendor, setSelectedVendor] = useState("");
const PF =
"https://firebasestorage.googleapis.com/v0/b/mern-stack-service-app.appspot.com/o/";
const navigate = useNavigate();
useEffect(() => {
init();
}, []);
const init = () => fetchServices();
const fetchServices = async () => {
const services = await HttpClient.get(`services`);
const vendors = await HttpClient.get(`vendor`);
setVednors(vendors);
setServices(services);
};
const handleChange = (event) => {
setSelectedVendor(event.target.value);
};
console.log(services);
return services.length === 0 ? (
<Grid>
<Typography
sx={{
fontSize: 32,
color: "blue",
display: "flex",
justifyContent: "center",
alignItems: "center",
textDecoration: "underline",
}}
>
Loading your services.....
</Typography>
</Grid>
) : (
<Grid container>
<ConfirmDialog />
<Grid container item xs={12} columnGap={2}>
<Box sx={{ float: "right" }}>
<Button
variant="outlined"
onClick={() => {
confirmDialog("Are you sure want to logout?", () => {
localStorage.clear();
navigate("/login");
});
}}
>
Logout
</Button>
</Box>
</Grid>
<Grid container item xs={12} rowGap={2}>
{services.map((service, index) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Card>
<CardActionArea>
<CardContent>
<Grid
component="img"
sx={{
height: 150,
width: 350,
maxHeight: { xs: 150, md: 175 },
maxWidth: { xs: 350, md: 375 },
objectFit: "cover",
}}
alt="The house from the offer."
src={
PF +
service.photo +
"?alt=media&token=c19f2d0a-f254-4391-b589-ef7ee3cad9f5"
}
></Grid>
<Grid item xs={12}>
<Typography textAlign="center">
{service.service}
</Typography>
</Grid>
<Grid item xs={12}>
<FormControl sx={{ minWidth: 120 }} size="small" fullWidth>
<InputLabel id="demo-select-small">
Select Vendor
</InputLabel>
<Select
size="small"
labelId="demo-select-small"
id="demo-select-small"
value={selectedVendor}
label="Select Vendor"
onChange={handleChange}
>
{vendors.map((vendor) => (
<MenuItem
key={vendor._id}
value={vendor.name}
id={vendor.name}
>
{vendor.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</CardContent>
</CardActionArea>
<CardActions>
<Grid item xs={12}>
<Button variant="outlined" color="primary" fullWidth>
Book Service
</Button>
</Grid>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</Grid>
);
};
export default Services;
I want the dropdown action reflect in the selected card.
It's because all of your mapped Select elements get their value from selectedVendor. you can change the code like this using these steps:
First:
Change the selectingVendor useState like this:
const [selectedVendors, setSelectedVendors] = useState([]);
Second:
And the handleChange function
const handleChange = (event,index) => {
const currentSelectedVendors = [...selectedVendors];
currentSelectedVendors[index] = event.target.value
setSelectedVendors(currentSelectedVendors);
};
Third:
You need to change the Select Mui component props.
<Select
size="small"
labelId="demo-select-small"
id="demo-select-small"
value={selectedVendors[index] || ""}
label="Select Vendor"
onChange={(e) => handleChange(e,index)}
>
Although there is another solution for this problem and you can pull <Select> logic out of Services component , I mean it's selectingVendor hook and it's change handler and create a new separate component which handles it.
PS: I think it should be better to use vendor._id as the value prop of your MenuItems

React-hook-form Uncontrolled error when using input mask with material UI textfield

when I'm using a controller with input mask and a text field of the material I can't access the errors at the time of submit.
I already tried to put a default value that they suggested in some similar questions but it didn't work.
Below I left some examples.
Follow the code and image below.
import { TextField } from "#mui/material";
import { Controller } from "react-hook-form";
import InputMask from "react-input-mask";
const InputCustom = ({
control,
errorFieldMessage,
name,
mask,
label,
}: any) => {
return (
<Controller
name={name}
control={control}
defaultValue=""
render={({ field: { onChange, value } }) => (
<InputMask mask={mask} value={value} onChange={onChange}>
{(inputProps: any) => (
<TextField
error={!!errorFieldMessage}
helperText={errorFieldMessage}
label={label}
variant="outlined"
type="text"
fullWidth
required
{...inputProps}
/>
)}
</InputMask>
)}
/>
);
};
when I leave it without the mask and submit, it works.
Follow the code and image below.
import { TextField } from "#mui/material";
import { Controller } from "react-hook-form";
import InputMask from "react-input-mask";
const InputCustom = ({
control,
errorFieldMessage,
name,
mask,
label,
}: any) => {
return (
<Controller
name={name}
control={control}
defaultValue=""
render={({ field: { onChange, value } }) => (
<TextField
error={errorFieldMessage}
label={label}
variant="outlined"
type="text"
fullWidth
required
onChange={onChange}
value={value}
/>
)}
/>
);
};

how to integrate tailwind into formik forms

I am trying to implement tailwindcss styles into Formik to style forms. But the styles declared through className are not being applied?
I think Formik is using classname to define input type and tailwind uses the same method to declare styles?
my form page:
import React from "react";
import * as yup from "yup"
import { Formik, Form } from 'formik'
import { MyTextInput } from "../components/FormParts";
let mainFormSchema = yup.object().shape({
name: yup.string().trim().required()
})
const tryMainForm = () => {
const initValues = {
name: ''
}
return (
<div>
<h1>Any place in your app!</h1>
<Formik
initialValues={initValues}
validationSchema={mainFormSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({ isSubmitting }) => (
<Form>
<MyTextInput
label="name"
name="name"
type="text"
placeholder="name"
/>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
</div>
)
}
export default tryMainForm
formpart component:
import { useField } from "formik";
export const MyTextInput = ({ label, ...props }) => {
const [field, meta] = useField(props)
return (
<>
<label htmlFor={props.id || props.name} className="block text-sm font-medium text-gray-700">{label}</label>
<input className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
)
}
can you suggest a way to deal with this?
tailwind applies styles properly to label but doesn't with input?

react hook forms and material ui: reset() not working after successfull form submit

I am trying to submit a form using react hook forms. After submit i want to clear all the fields. I have read about using reset(). But its not working
import React, { Fragment } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as Yup from "yup";
import "react-toastify/dist/ReactToastify.css";
import {
Paper,
Box,
Grid,
TextField,
Typography,
Button,
} from "#material-ui/core";
export default function ResetPassword() {
const validationSchema = Yup.object().shape({
old_password: Yup.string().required("Password is required"),
new_password1: Yup.string().required("Password is required"),
new_password2: Yup.string().required("Password is required"),
});
const { register, handleSubmit, reset } = useForm({
resolver: yupResolver(validationSchema),
});
const onSubmit = (data) => {
console.log(data);
reset();
};
return (
<Fragment>
<Paper variant="outlined">
<Box px={3} py={2}>
<Typography variant="h6" align="center" margin="dense">
Change Password
</Typography>
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<TextField
required
label="Current Password"
type="password"
{...register("old_password")}
/>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
required
label="New Password"
type="password"
{...register("new_password1")}
/>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
required
label="Confirm New Password"
type="password"
{...register("new_password2")}
/>
</Grid>
</Grid>
<Box mt={3}>
<Button
variant="contained"
color="primary"
onClick={handleSubmit(onSubmit)}
>
Change Password
</Button>
</Box>
</Box>
</Paper>
</Fragment>
);
}
How to reset the fields after submit
You have to use RHF's <Controller /> component here as register won't work with MUI's <Textfield /> because it is an external controlled component. You can find here more information about integrating UI libraries.
One important thing is to pass defaultValues to useForm, as this is required when using reset for external controlled components (Docs).
You will need to pass defaultValues to useForm in order to reset the
Controller components' value.
export default function ResetPassword() {
const validationSchema = Yup.object().shape({
old_password: Yup.string().required("Password is required"),
new_password1: Yup.string().required("Password is required"),
new_password2: Yup.string().required("Password is required")
});
const { control, handleSubmit, reset } = useForm({
resolver: yupResolver(validationSchema),
defaultValues: {
old_password: "",
new_password1: "",
new_password2: ""
}
});
const onSubmit = (data) => {
console.log(data);
reset();
};
return (
<Fragment>
<Paper variant="outlined">
<Box px={3} py={2}>
<Typography variant="h6" align="center" margin="dense">
Change Password
</Typography>
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<Controller
name="old_password"
control={control}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Current Password"
type="password"
/>
)}
/>
</Grid>
<Grid item xs={12} sm={12}>
<Controller
name="new_password1"
control={control}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="New Password"
type="password"
/>
)}
/>
</Grid>
<Grid item xs={12} sm={12}>
<Controller
name="new_password2"
control={control}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Confirm New Password"
type="password"
/>
)}
/>
</Grid>
</Grid>
<Box mt={3}>
<Button
variant="contained"
color="primary"
onClick={handleSubmit(onSubmit)}
>
Change Password
</Button>
</Box>
</Box>
</Paper>
</Fragment>
);
}