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
Related
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>
I have quite a large form with many textfields inside, there are about 60-70 textfields inside.
Certainly it begins to lag and I know that the re-rendering could be a problem, also of the reason of having all values in the component state. I have no idea how to split it correctly or how I should solve that.
Main Component -> View
const [values, setValues] = useState({
value1: '',
...
value60: '',
});
return (
<div className={classes.root}>
<form>
<Grid container spacing={2}>
<Grid item xl={12}>
<Breadcrumb breadcrumb={breadcrumb}/>
</Grid>
<BlockInputBasic handleChange={handleChange.bind()} values={values}/>
<BlockInputTarget handleChange={handleChange.bind()} values={values}/>
</Grid>
</form>
</div>
);
Child Component
export default function BlockInputBasic(props) {
return (
<TextField
fullWidth
label="Name"
name="name"
onChange={props.handleChange}
required
value={props.values.name}
variant="outlined"
/>
<TextField
fullWidth
label="Name2"
name="name2"
onChange={props.handleChange}
required
value={props.values.name}
variant="outlined"
/>
....
<TextField
fullWidth
label="Adress60"
name="Adress60"
onChange={props.handleChange}
required
value={props.values.name}
variant="outlined"
/>
);
}
I really appreciate your help!
You could initialise state with same keys as name prop of textfields
const [values, setValues] = useState({
name1: '',
...
name60: '',
});
Set state in event handler as
const handleChange = (e) => {
setValues({
...values,
[e.target.name]: e.target.value
})
}
<TextField
fullWidth
label="Name"
name="name1"
onChange={props.handleChange}
required
value={props.values.name1}
variant="outlined"
/>
.
.
.
.
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
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
I am trying to use Formik and Material UI. This is a very basic example which am trying to work on. However, I am getting the following error: React.Children.only expected to receive a single React element child.
This error comes at the line where Formik begins,i.e. :
export default function sample() {
const classes = useStyles({});
const [sent, setSent] = React.useState(false);
const handleSubmit = () => {
console.log('done');
};
return (
<Row type="flex">
<Typography variant="h3" >
YO
</Typography>
<Typography variant="body2" align="center">
YO YO
</Typography>
<Formik
initialValues={{
fname: '',
lname: '',
}}
validationSchema={YOYOSCHEMA}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<Field
autoFocus
component={CustomTextField}
autoComplete="fname"
fullWidth
label="First name"
name="firstName"
required
/>
</Grid>
<Grid item xs={12} sm={6}>
<Field
component={CustomTextField}
autoComplete="lname"
fullWidth
label="Last name"
name="lastName"
required
/>
</Grid>
</Grid>
</Form>
)};
</Formik>
</Row >
)
}
EDIT:
Here is the customtextfield code. Internally its using TextField of the Material UI:
const CustomTextField = ({
autoComplete,
input,
InputProps,
meta: { touched, error, submitError },
...other
}: { autoComplete: any, input: any, InputProps: any, meta: any }) => (
<TextField
error={Boolean(touched && (error || submitError))}
{...input}
{...other}
InputProps={{
inputProps: {
autoComplete,
},
...InputProps,
}}
helperText={touched ? error || submitError : ''}
/>
);
I am using YUP for validation.
How to fix the error? Thanks.