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

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

Related

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 17 wrap mui tab in form how to control form submission with react hook form

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!!

Flexgrowth seems not to work wih DataGrid

The size of the main area of the DataGrid changes with the window size but seems to ignore the size required for the page footer (so a scroll bar remains).
export default class DebugDisplay extends Component {
render = () => {
return <Box >
<DataAdapter localDbConnect="items" setAccessor={this.setAccessor}/>
<AppBar position="static">
<Toolbar>
<IconButton color="inherit" aria-label="Menu" onClick={this.toggleMenu}>
<MenuIcon />
</IconButton>
<Typography variant="h6" sx={{flexGrow:1, flexDirection: "row"}}>
POS Web Debug {this.state.timestamp}
</Typography>
</Toolbar>
</AppBar>
<Drawer type="temporary" open={this.state.debugMenuOpen} onClose={this.toggleMenu}>
<Box sx={{display: "flex", flexDirection: "column", flexGrow: 1}}>
<Button onClick={this.toggleMenu}>Close</Button>
<Divider />
<Button onClick={this.testEnter}>Test Enter</Button>
<Button onClick={this.testTotal}>Test Total</Button>
<Button onClick={this.testInvoice}>Test Invoice</Button>
</Box>
</Drawer>
<Paper >
<Button onClick={this.testEnter} variant="outlined">Test Enter</Button>
<Button onClick={this.testTotal} variant="outlined">Test Total</Button>
<Button onClick={this.testInvoice} variant="outlined">Test Invoice</Button>
<DataGrid sx={{display: "flex", flexGrow:1, flexDirection: "column"}} rows={this.state.rows} columns={itemColumns} autoPageSize pagination density="compact" onCellClick={this.onCellClick}/>;
</Paper>
</Box>;
}
What is wrong?

Fix dialog material ui

I have a dialog with material ui where i have a text and then 4 fields textfield.
The problem is that the elements inside dialogcontent doesnt fill all the space in the dialog leaving one blank space on the right side.
I would like to give some space between the textfields in the same row as well as space-evenly these elements.
This is my dialog definition:
import React from "react";
import Dialog from "#material-ui/core/Dialog";
import DialogTitle from "#material-ui/core/DialogTitle";
import {
DialogActions,
Button,
DialogContent,
DialogContentText,
} from "#material-ui/core";
import { faExclamationTriangle } from "#fortawesome/free-solid-svg-icons";
import { addProduct } from "../view/Items/Items";
import { useState } from "react";
import { TextField } from "#material-ui/core";
import { updateValues } from "../view/Items/Items";
import { Grid } from "#material-ui/core";
export default function AlertPopOverParametros({
producto,
año,
open,
setOpen,
onAccept,
}) {
const [coef_transp, setCoefTransp] = useState();
const [coef_comer, setCoefComer] = useState();
const [km, setKm] = useState();
const [prod, setProd] = useState();
return (
<Dialog fullWidth aria-labelledby="simple-dialog-title" open={open}>
<DialogTitle id="simple-dialog-title">
<b>Configuración del producto</b>
</DialogTitle>
<DialogContent>
<DialogContentText>
Introduzca los valores necesarios para el cálculo del valor de huella
hídrica
</DialogContentText>
<TextField
onChange={(e) => setCoefTransp(e.target.value)}
autoFocus
label="Coeficiente de Transporte"
type="number"
value={coef_transp}
/>
<TextField
onChange={(e) => setCoefComer(e.target.value)}
autoFocus
label="Coeficiente de Comercialización"
type="number"
value={coef_comer}
/>
<TextField
onChange={(e) => setKm(e.target.value)}
autoFocus
label="Kilómetros"
type="number"
value={km}
/>
<TextField
onChange={(e) => setProd(e.target.value)}
autoFocus
label="Producción"
type="number"
value={prod}
/>
</DialogContent>
<DialogActions>
{onAccept ? (
<Button
style={{
backgroundColor: "#0068ff",
color: "white",
borderRadius: "5px",
}}
fullWidth
onClick={() => {
onAccept();
console.log(producto, año, coef_transp, coef_comer, km, prod);
updateValues(producto, año, coef_transp, coef_comer, km, prod);
setOpen(false);
}}
>
Aceptar
</Button>
) : null}
</DialogActions>
</Dialog>
);
}
I have tried to wrap everything in a Grid element and specifying the size of it but it didn't work.
Thank you so much.
Using a Grid and fullWidth property on the Textfields will solve your issue:
<Grid container>
<Grid xs={6}>
<TextField
fullWidth
...
/>
</Grid>
<Grid xs={6}>
<TextField
fullWidth
...
/>
</Grid>
<Grid xs={6}>
<TextField
fullWidth
...
/>
</Grid>
<Grid xs={6}>
<TextField
fullWidth
...
/>
</Grid>
</Grid>
The Grid will order the textfields into two rows and two columns by setting xs to 6 on the grid childs. Then in order to make your textfields span the entire width of the grid child you can use the fullWidth property.

Form Input value property not behaving as expected

I am trying to create a form to collect input to create an event listing.
I am using the 'value' property in way it is documented in the React docs:
https://reactjs.org/docs/forms.html
,to collect user input onChange, but material-ui:
https://material-ui.com/api/input/
uses 'value' for setting value to the input field not collecting the value in the field. This is causing all sort of bugs, e.i. not being able to enter input, pickers not displaying default values and not accepting values and also not being able to collect information. I could use pointers on where I am going wrong. Thanks for having a look :-)
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Input from '#material-ui/core/Input';
import Paper from '#material-ui/core/Paper';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
}
});
class CreateEvent extends React.Component{
constructor() {
super();
this.state = {
title: "",
description: "",
location: "",
start_time: "",
start_date: "",
end_time: "",
end_date: ""
};
}
updateInput = e => {
this.setState({
[e.target.name]: e.target.value
});
};
cancelCreation = e =>{
};
addNewEvent = e =>{
//Make db call
};
render(){
return(
<div className="form-container">
<Paper className="new-event-form">
<form className="event-form" onSubmit={this.addNewEvent}>
<Input
type="text"
placeholder="Event title"
className="event-title"
inputProps={{
'aria-label': 'Description',
}}
multiline={1}
rows={1}
onChange={this.updateInput}
value={this.state.value}
/>
<Input
type="text"
placeholder="Event description"
className="event-title"
inputProps={{
'aria-label': 'Description',
}}
multiline={true}
rows={10}
onChange={this.updateInput}
//value={this.state.description}
/*
Will allow user to provide input because 'value' is commented out. But the above component will not because value is referenced
*/
/>
<Grid container spacing={16} className="event-grid">
<Grid item xs={4}>
<Input
type="text"
item xs={4}
placeholder="Event location"
className="event-location"
inputProps={{
'aria-label': 'Description',
}}
multiline={true}
rows={4}
onChange={this.updateInput}
//value={this.state.location}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="date"
label="Start date"
type="date"
defaultValue="2017-05-24"
className="event-start-date"
InputLabelProps={{
shrink: true,
}}
onChange={this.updateInput}
value={this.state.start_date}
/>
<TextField
id="time"
label="Start time"
type="time"
defaultValue="07:30"
className="event-start-time"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="date"
label="End date"
type="date"
defaultValue="2017-05-24"
className="event-start-date"
InputLabelProps={{
shrink: true,
}}
/>
<TextField
id="time"
label="End time"
type="time"
defaultValue="07:30"
className="event-start-time"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
/>
</Grid>
</Grid>
<Button className="line-spacer"/>
<Grid container className="form-buttons">
<Grid item xs={12}>
<Input type="file" name="my-event-image" id="file" className="new-event-image"> </Input>
<label htmlFor="file">CHOOSE AN IMAGE</label>
<Button className="line-spacer" onChange={this.updateInput}/>
</Grid>
</Grid>
<Grid container spacing={16} className="form-buttons">
<Grid item xs={6}>
<Button onChange={this.cancelCreation}>Cancel</Button>
</Grid>
<Grid item xs={6}>
<Button type="submit">Submit</Button>
</Grid>
</Grid>
</form>
</Paper>
</div>
);
}
}
export default withStyles(styles)(CreateEvent);
In your updateInput method you use e.target.name but none of your input fields have a name property. Add a name property on each Input component matching the name you are using in your value. For example:
<Input name="description" ... value={this.state.description} .../>