react-hook-form undo the form to previous state - reactjs

i populated a form with react-hook-form's setValue function (i don't know if it is the best way to set up a form in edit mode).
Once the form was touched from a user, i want (on a button click) to restore the form to the state i've previously setted,
(pay attention that i don't want to reset it but, make it again to the value i've previously setted)
const { register, handleSubmit, watch, reset, errors, setValue } = useForm();
const { id } = useParams()
const { loading, error, data } = useQuery(GET_FBUSER,{
variables: {_id: id},
skip: id === undefined
});
useEffect(() => {
if (data) {
const {_id, id, fbId, name} = data.FBUser[0]
setValue('_id',_id);
setValue('id',id);
setValue('fbId',fbId);
setValue('name',name);
}
}, [data])
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={3}>
<Grid item xs={4}>
<TextField fullWidth inputRef={register} InputLabelProps={{ shrink: true }} InputProps={{readOnly: true}} name="_id" label="_Id" variant="outlined" />
</Grid>
<Grid item xs={8}>
<TextField fullWidth inputRef={register} InputLabelProps={{ shrink: true }} InputProps={{readOnly: true}} name="id" label="Id" variant="outlined" />
</Grid>
<Grid item xs={12}>
<TextField fullWidth inputRef={register} InputLabelProps={{ shrink: true }} name="name" label="Name" variant="outlined" />
</Grid>
<Grid item xs={12}>
<TextField fullWidth error={errors.fbId} inputRef={register({required : true})} InputLabelProps={{ shrink: true , required: true}} name="fbId" label="Facebook Id" variant="outlined" />
</Grid>
<Grid item xs={12}>
<TextField fullWidth inputRef={register} InputLabelProps={{ shrink: true }} name="note" label="Note" variant="outlined" />
</Grid>
<Grid item xs={12}>
<Button type="submit" variant="contained" color="primary" startIcon={<SaveIcon/>}>Salva</Button>
<Button onClick={reset} variant="contained" color="primary" startIcon={<CancelIcon/>}>Annulla</Button>
</Grid>
</Grid>
</form>

You should pass an object with form fields values in reset method.
reset({
type: "contact",
firstName: "John",
lastName: "Doe"
})
If you set default initial values in useForm hook, invoking reset() result in form fields setted to your initial values, but if you pass an object with different data, the fields are setted to values you passed.
So in your case you should save the form state in a particular moment, maybe with getValues(), then on button click set the values you wanted.
Docs:
Reset - React Hook Form
Example:
Reset Example - Codesandbox

Related

Update TextField from MUI in conjunction with react-hook-form. onChange is not being called

const [userData, setUserData] = useState({
fullName: "",
email: "",
}); // Local component state
const onChange = (event) => {
console.log(event.target.value);
}; // Evente handler
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<TextField
required
id="name"
name="fullName"
onChange={onChange}
label="Name"
InputLabelProps={{ shrink: true }}
fullWidth
value={userData.fullName}
margin="dense"
/>
<Typography variant="inherit" color="textSecondary">
{errors.fullName?.message}
</Typography>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
required
id="email"
name="email"
label="Email"
onChange={onChange}
fullWidth
margin="dense"
value={userData.email}
{...register("email")}
error={errors.email ? true : false}
/>
<Typography variant="inherit" color="textSecondary">
{errors.email?.message}
</Typography>
</Grid>
For some reason, onChange is not being called. I also using Yup for validation. I need to update the input value and send it to the API. But for some reason event handler is not being called
You're overriding the onChange prop with the spread of {...register("email")} as the register call will return an object, where one property is named onChange, which RHF needs to update it's internal form state. Because of that you simply don't need your own onChange handler when you use RHF, as you could just access the current <TextField /> value via RHF directly. You just have to pass your default values either via useForm defaultValues or pass it to <Controller /> directly instead of setting it via the value prop directly.
I would also recommend to use <Controller /> as with register you are losing the functionality of setting up the correct ref for your <TextField /> input element as it is linked via the inputRef prop instead of using ref which RHF register uses. This comes in handy when you want to instantly focus a field when validiation fails upon submitting the form.
You can also use <TextField />'s error and helperText props to display your error and access it via the <Controller /> fieldState object to get the current validation error (if there is one).
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<Controller
control={control}
name="fullName"
defaultValue={userData.fullName}
render={({ field: { ref, ...field }, fieldState: { error } }) => (
<TextField
required
id={field.name}
label="Name"
InputLabelProps={{ shrink: true }}
fullWidth
margin="dense"
error={!!error}
helperText={error?.message}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={12}>
<Controller
control={control}
name="email"
defaultValue={userData.email}
render={({ field: { ref, ...field }, fieldState: { error } }) => (
<TextField
required
id={field.name}
label="Name"
InputLabelProps={{ shrink: true }}
fullWidth
margin="dense"
error={!!error}
helperText={error?.message}
/>
)}
/>
</Grid>
</Grid>

React material-ui multiple checkboxes and onChange not working

I have multiple checkboxes in an edit modal form, which has a record structure for update, containing the inputs including the checkboxes in state.
There is one onChange event for the inputs.
When I click on a checkbox, the onChnage 'handleInputChanges' does execute.
The evt.target.checked is true or false.
The evt.target.name is correct and matches the name in the updateRecordStructure.
But the checkbox won't display the checked or unchecked status.
The markup:
<Grid item xs={5}>
<FormControlLabel variant="outlined" size="small"
control={<Checkbox
checked={defaultChecked}
color="primary"
name={name}
onChange={handleInputChanges}/>
}
label={label}
id={id}
/>
</Grid>
const updateRecordStructure ={
id: 0,
name: '',
canDo1b: false,
canDo1a: false,
canDo2b: false,
canDo2a: false
};
const [editRecordState, setEditRecordState] = React.useState(
updateRecordStructure
);
const handleInputChanges = (evt) => {
// Distinguish which input the change is coming from using the target.name.
// The square brackets around target.name, creates a dynamic key of that targets name in the object.
if (evt.target.name !== '') {
const value = evt.target.value;
if (evt.target.checked) {
setEditRecordState({
...editRecordState,
[evt.target.name]: evt.target.checked
});
} else {
setEditRecordState({
...editRecordState,
[evt.target.name]: value
});
}
}
};
Your state is not even connected to the check box.
Your code:
<Grid item xs={5}>
<FormControlLabel variant="outlined" size="small"
control={
<Checkbox
checked={defaultChecked} // You are supposed to specify your state here
color="primary"
name={name}
onChange={handleInputChanges}
/>
}
label={label}
id={id}
/>
</Grid>
i.e.
<Grid item xs={5}>
<FormControlLabel variant="outlined" size="small"
control={
<Checkbox
checked={editRecordState[name]}
color="primary"
name={name}
onChange={handleInputChanges}
/>
}
label={label}
id={id}
/>
</Grid>
If you wish to make certain checkboxes checked by default, update updateRecordStructure instead.

useFormik React - Update initialValues

I am using useFormik hook for my data handling. Adding the data is fine, but when I want to update the data in the same component I need to change the initial values to the custom object instead of having empty strings. Is there formik way of doing that with useFormik Hook? although I can achieve that with states, but then what will be the benefit of using the formik all along.
Kindly help!
const formik = useFormik({
initialValues: {
name: '',
post: '',
contact: '',
email: '',
password: '',
},
onSubmit: values => {
//handling submit
},
});
Form:
<form className={classes.form} onSubmit={formik.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
autoComplete="name"
variant="outlined"
required
fullWidth
label="Name"
autoFocus
id="name"
name="name"
onChange={formik.handleChange}
value={formik.values.name}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
variant="outlined"
required
fullWidth
label="Post"
autoComplete="post"
id="post"
name="post"
onChange={formik.handleChange}
value={formik.values.post}
/>
</Grid>
//Submit button and form close etc
Thanks
I found the solution that is:
For One field
const editEmployee = ()=> {
formik.setFieldValue("name","Imran");
alert("changed")
}
For Multiple:
formik.setValues({"name":"hey","post":"hoii"});
or send the whole Object

React Formik Material UI Autocomplete: How can I populate value inside of autocomplete from localStorage?

So, I'm trying to create form with Formik and Material UI. For all the fields everything is working as it should but the problem is with Autocomplete. I cannot find the way to populate the field from the localStorage. I tried everything, from putting the value props, inputValue, defaultValue, etc but nothing seems to work.
import React from 'react'
import { Grid } from '#material-ui/core'
import { Autocomplete } from '#material-ui/lab'
import { Formik, Form, Field } from 'formik'
import { TextField } from 'formik-material-ui'
import * as yup from 'yup'
import { nationality } from '../../constants/nationality'
import Button from '../Button/Button'
export default function ForeignAddress () {
let localStorageData = localStorage.getItem('foreignAddress'),
retrivedData = JSON.parse(localStorageData)
const handleNextClick = () => {
console.log('clicked next')
}
const handleBackClick = () => {
console.log('clicked back')
}
const validationSchema = yup.object({
streetName: yup.string().required('Street name is required'),
streetNumber: yup.string().required('Street number is required'),
postalCode: yup.string().required('Postal code is required'),
city: yup.string().required('City is required'),
country: yup.string().required('Country is required'),
})
console.log(retrivedData)
return (
<React.Fragment>
<div className="pages-wrapper address">
<Formik
initialValues={retrivedData ? retrivedData : {streetName: '', streetNumber: '', postalCode: '', city: '', coAddress: '', country: ''}}
onSubmit={(data) => {
console.log(data)
localStorage.setItem('foreignAddress', JSON.stringify(data))
handleNextClick()
}}
validationSchema={validationSchema}
>
{({setFieldValue}) => (
<Form>
<Grid container spacing={3}>
<Grid item xs={12} md={8}>
<Field component={TextField} name="streetName" label="Street Name" variant="outlined" fullWidth />
</Grid>
<Grid item xs={12} md={4}>
<Field component={TextField} name="streetNumber" label="Street Number" variant="outlined" fullWidth />
</Grid>
<Grid item xs={12} md={4}>
<Field component={TextField} name="postalCode" label="Postal Code" variant="outlined" fullWidth />
</Grid>
<Grid item xs={12} md={8}>
<Field component={TextField} name="city" label="City" variant="outlined" fullWidth />
</Grid>
<Grid item xs={12} md={6}>
<Field component={TextField} name="coAddress" label="C/O Address" variant="outlined" fullWidth />
</Grid>
<Grid item xs={12} md={6}>
<Autocomplete
id="foreignCountry"
className="country-select"
name="country"
options={nationality}
getOptionLabel={option => option.label}
onChange={(e, value) => {
console.log(value)
setFieldValue("country", value.code)
}}
renderInput={params => (
<Field component={TextField} {...params} name="country" label="Country" variant="outlined" fullWidth/>
)}
/>
</Grid>
</Grid>
<div className="button-wrapper">
<Button label="Back" go="back" handleClick={handleBackClick}/>
<Button label="Next" go="next" type="submit" />
</div>
</Form>
)}
</Formik>
</div>
</React.Fragment>
)
}
EDIT:
Thanks to #Vencovsky i was able to get it done
in case someone in the future needs this here is the working code.
Just change Autocomplete component to
<Autocomplete
id="foreignCountry"
className="country-select"
name="country"
options={nationality}
getOptionLabel={option => option.label}
defaultValue={values.country}
onChange={(e, value) => {
console.log(value)
setFieldValue("country", value)
}}
renderInput={params => (
<Field component={TextField} {...params} name="country" label="Country" variant="outlined" fullWidth/>
)}
/>
and in the Formik props just add values prop
{({setFieldValue, values}) => (
<Form>,...
Edit:
There is a few thing you need to change.
First you can't just store the code to load it later, you need to store everything (the hole value object) from the options.
Change the initial value of country: '' to country: {code: "", label: "", phone: ""} which is all the default value of the options.
Then to load the value correctly you should pass value={values.country} where values comes from formik props.
And on onChange you should save the hole value onChange={(e, value) => {setFieldValue("country", value); }}
But you are also importing and using some wrong things like
<Field
component={TextField}
{...params}
name="country"
label="Country"
variant="outlined"
fullWidth
/>
Where Field is form formik and TextField from formik material ui.
Not sure why you use it like that, but I have changed it.
Here is a working example

Formik values empty

I have the following Formik form
<Formik
initialValues={{
email: '',
password: ''
}}
onSubmit={(values, { setSubmitting }) => {
console.log(JSON.stringify(values));
setSubmitting(true);
fetch('/login', {
method: 'POST',
body: JSON.stringify(values),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(response => {
console.log(response);
setSubmitting(false);
if (!response.success) return showAlert();
login(response.user);
history.push('/');
})
.catch(console.log);
}}
>
{({ isSubmitting }) => (
<Form className={classes.form}>
<TextField
required
variant="outlined"
margin="normal"
fullWidth
label="Email"
name="email"
autoFocus
/>
<TextField
required
variant="outlined"
margin="normal"
fullWidth
name="password"
label="Wachtwoord"
type="password"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={isSubmitting}
>
Log in
</Button>
<Grid container>
<Grid item xs>
<Link href="/register" variant="body2">
{'Nog geen account? Registreer nu!'}
</Link>
</Grid>
</Grid>
</Form>
)}
</Formik>
For some reason the values in the onSubmit are empty, how can I set it to the values inside the form? The only difference between this form and another one I got to work is that this one does not have a validation schema but I can not imagine that is required
You should take a look at the example in formik's docs.
TextField isn't connect to Formik. When the value of TextField changes, Formik don't change, you need Field (import { Field } from 'formik') that will render TextField.
Example for email
<Field
name="email"
render={({ field /* _form */ }) => (
<TextField
required
variant="outlined"
margin="normal"
fullWidth
label="Email"
autoFocus
{...field}
/>
)}
/>
If you are using a external input, you should use this way.
You need to pass this
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
to the Textinput, and for Password aswell.
Your Textinput doesn't track any changes or submission.
Check here

Resources