Here is the code that I've used for the controller:
import React from 'react';
import { useFormContext, Controller } from 'react-hook-form';
import { TextField, Grid } from '#material-ui/core';
function FormInput({ name, label, required }) {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
as={TextField}
name={name}
control={control}
label={label}
fullWidth
required={required}
/>
</Grid>
);
}
export default FormInput;
Here is where I call FormInput:
import React from 'react'
import { InputLabel, Select, MenuItem, Button, Grid, Typography } from '#material-ui/core'
import { useForm, FormProvider } from 'react-hook-form'
import FormInput from './CustomTextField'
const AdressForm = () => {
const methods = useForm()
return (
<>
<Typography variant="h6" gutterBottom>Shippng Adress</Typography>
<FormProvider {...methods}>
<form onSubmit=''>
<Grid container spacing={3}>
<FormInput required name="firstName" label="First name" />
</Grid>
</form>
</FormProvider>
</>
)
}
export default AdressForm
And I get this error:
"TypeError: props.render is not a function"
I'm trying to use the react-hook-form to make the form in the adressForm but I have a problem with the controller.
According to the doc, the as props have been removed in react-hook-form's v7 in favor of render props.
Documentation: https://react-hook-form.com/api/usecontroller/controller
Related
I have a Formik form that uses Yup for validation. One of my fields is a Datepicker, but I am unable to integrate it into yup validation.
Below is code which works as far as rendering the component, but as soon as I try wrap <DatePicker/> inside <Field name="date></Field> tags, nothing renders.
const [fieldDate,setFieldDate] = useState ("");
const dateSchema = Yup.object().shape({
date: Yup.date().required('Date is required'),
});
const initialValues = {date:''};
return (
<div>
<FormContainer>
<Formik
initialValues={initialValues}
//validationSchema={loginValidationSchema}
validationSchema={dateSchema}
onSubmit={()=>{console.log ("ok")}}
>
{({ isSubmitting, values, setFieldValue, handleChange, handleBlur, errors, touched }) => (
<Form className="form">
<LocalizationProvider dateAdapter={DateFnsUtils}>
<DatePicker
label="Date"
value={fieldDate}
onChange={(newValue:any) => {
setFieldDate(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
<div className="buttonWrapper">
<SubmitButton type="submit" className="SubmitButton">Submit</SubmitButton>
</div>
</Form>
)}
</Formik>
</FormContainer>
</div>
And here are my imports...I'm including them because I find that there are multiple libraries with the same name from MUI that have different parent directories with different requirements which has somewhat added to my confusion when trying to solve this via online solutions:
import React, { useState} from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import {FormContainer,FieldContainer, SubmitButton } from "../GlobalStyle";
import { TextField } from "#mui/material";
import * as Yup from "yup";
import "react-datepicker/dist/react-datepicker.css";
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import DateFnsUtils from "#date-io/date-fns";
import { DatePicker } from '#mui/x-date-pickers/DatePicker';
Thanks!
import React from "react";
import { Formik, Form, Field, } from "formik";
import { TextField } from "#mui/material";
import * as Yup from "yup";
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import DateFnsUtils from "#date-io/date-fns";
import { DatePicker } from '#mui/x-date-pickers/DatePicker';
const dateSchema = Yup.object().shape({
date: Yup.date().required('Date is required'),
});
const initialValues = { date: '' };
export const App = () => {
return (
<div>
<Formik
initialValues={initialValues}
validateOnMount
validationSchema={dateSchema}
onSubmit={() => {
console.log('ok');
}}
>
<Form className="form">
<Field name="date">
{({ field, meta, form }) => (
<LocalizationProvider dateAdapter={DateFnsUtils}>
<DatePicker
label="Date"
renderInput={(params) => <TextField {...params} />}
// Get your date value from your form
value={field.value}
// Set your date in the form
onChange={(date) => form.setFieldValue('date', date)}
/>
{/* Error from yup */}
<div>{meta.error}</div>
</LocalizationProvider>
)}
</Field>
<div className="buttonWrapper">
<button type="submit" className="SubmitButton">
Submit
</button>
</div>
</Form>
</Formik>
</div>
);
};
export default App;
I want to create a form by using react-hook-form, but I am having trouble with it. As I found, there was a big change at v7 so the code I used as a reference is not working. I tried to modify it, and I figured out the problem is with the registering, but I cannot pass that name parameter dynamically.
My main component.
import React from 'react';
import i18next from 'i18next';
import { Button, Grid, Typography } from '#material-ui/core';
import { useForm, FormProvider } from 'react-hook-form';
import { Link } from 'react-router-dom';
import FormInput from './CustomTextField'
const AddressForm = ({ next }) => {
const methods = useForm();
return (
<>
<Typography variant="h6" gutterBottom>
{i18next.t('shipping_address')}
</Typography>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit((data) => {
console.log(data);
next({ ...data })})}>
<Grid container spacing={3}>
<FormInput required register={methods.register} name='lastName' label={i18next.t('last_name')} />
<FormInput required register={methods.register} name='firstName' label={i18next.t('first_name')} />
<FormInput required register={methods.register} name='email' label={i18next.t('mail')} />
<FormInput required register={methods.register} name='zip' label={i18next.t('zip_code')} />
<FormInput required register={methods.register} name='city' label={i18next.t('city')} />
<FormInput required register={methods.register} name='address1' label={i18next.t('address_1')} />
</Grid>
<br />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button component={Link} to="/cart" variant="outlined">
{i18next.t('back_to_cart')}
</Button>
<Button type="submit" variant="contained" color="primary">
{i18next.t('next_step')}
</Button>
</div>
</form>
</FormProvider>
</>
)
}
export default AddressForm
CustomTextField component:
import React from 'react';
import { TextField, Grid } from '#material-ui/core';
import { useFormContext, Controller } from 'react-hook-form';
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={name}
render = {(field) => (
<TextField
{...register({name})} // <--- It is not working like this
label={label}
required={required}
/>
)}
/>
</Grid>
)
}
export default CustomTextField
By the doc: https://react-hook-form.com/api/useform/register
it takes the input field's name as a parameter, if I pass it in as a string, it works fine. How can I pass the name's value as a parameter to the register() function?
The problem is you're mixing RHF's <Controller /> and register. As Mui's <TextField /> is an external controlled component you should use <Controller />. Check the docs here for more info.
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={name}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
label={label}
required={required}
/>
)}
/>
</Grid>
)
}
If you really want to use register here, you have to remove the wrapping <Controller /> and pass a name as a string instead as an object like you are doing right now. But i would 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.
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<TextField
{...register(name)}
label={label}
required={required}
/>
</Grid>
)
}
Hi im trying to do one form with react-hook-form and material-ui. I don't want to write Controller every time for all TextFields. Because of that i declare it in another file and call it in my form but its not working i didn't understand why, because in some videos that i watched is working. What is the problem and how i can fix it ?
Form Field
import React from 'react'
import { TextField, Grid } from '#material-ui/core'
import { useForm, Controller, useFormContext } from 'react-hook-form'
const FormField = ({name, label}) => {
const { control } = useForm()
return (
<Grid item xs={12} sm={6} >
<Controller
render = {({field}) =>
<TextField
{...field}
label={label} required
/>}
name={name}
control = {control}
defaultValue=""
/>
</Grid>
)
}
export default FormField
Adress Form
import React from 'react'
import { InputLabel, Select, MenuItem, Button, Grid, Typography, TextField } from '#material-ui/core'
import { useForm, FormProvider, Controller } from 'react-hook-form'
import FormField from './FormField'
import { Link } from 'react-router-dom'
const AdressForm = ({next}) => {
const {handleSubmit, control} = useForm()
return (
<>
<Typography variant="h6" gutterBottom>Shipping Address </Typography>
<form onSubmit={handleSubmit((data) => console.log(data) )}>
<Grid container spacing={3}>
<FormField name='firstName' label='First Name' required='required'/>
<FormField name='lastName' label='Last Name' />
<FormField name='email' label='Email' />
<FormField name='phoneNumber' label='Phone Number' />
</Grid>
<br/>
<div style={{ display: 'flex', justifyContent: 'space-between'}}>
<Button component={Link} to="/cart" variant="outlined">Back to Cart</Button>
<Button type="submit" variant="contained" color="primary">Next</Button>
</div>
</form>
</>
)
}
export default AdressForm
You must use one useForm hook for each form, in your code, you call useForm in every Field components, creating multiple independent form states, which leads to unexpected result.
What you need to do is to call useForm in the parent element and pass the dependencies (register, formState, error...) down the child components, so your form can have one unified state. If you have a deeply nested components, you can use useFormContext to pass the form context to the nested children easily:
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input {...register("test")} />;
}
I have a minimalist function whereby I'm providing two choices in the form of radio inputs: "bull" or "bear".
Full code below:
import React, { useState } from 'react';
import RadioButton from 'material-ui/RadioButton';
const styles = {
rootRadio: {
//left:"37%",
//position:'absolute'
}
}
function RadioComp() {
const [riskP, setRiskP] = useState("bull")
const handleRisk = (e) => {
setRiskP(e.target.value)
}
return (
<React.Fragment>
<h2>Risk profile</h2>
<div>
<span>Bull</span>
<RadioButton
style={styles.rootRadio}
value='bull'
checked={riskP==='bull'}
onChange={handleRisk}
/>
</div>
<div>
<span>Bear</span>
<RadioButton
style={styles.rootRadio}
value='bear'
checked={riskP==='bear'}
onChange={handleRisk}
/>
</div>
</React.Fragment>
)
}
export default RadioComp;
As seen above, I have tried to configure this logic using useState():
const [riskP, setRiskP] = useState("bull")
const handleRisk = (e) => {
setRiskP(e.target.value)
}
However, in the view, clicking on "bear" doesn't do anything. It seems to be locked on "bull."
Question
You have to use RadioGroup with FormControl option.
import React from 'react';
import Radio from '#material-ui/core/Radio';
import RadioGroup from '#material-ui/core/RadioGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
export default function RadioButtonsGroup() {
const [value, setValue] = React.useState('female');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<FormControl component="fieldset">
<FormLabel component="legend">Gender</FormLabel>
<RadioGroup name="gender1" value={value} onChange={handleChange}>
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
<FormControlLabel value="other" control={<Radio />} label="Other" />
</RadioGroup>
</FormControl>
);
}
I'm using redux-form to create an authentication form.
import React from 'react';
import Button from '#material-ui/core/Button';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import Grid from '#material-ui/core/Grid';
import TextField from '../../components/TextFieldWrapper';
const Authentication = ({
classes,
submitting,
handleSubmit,
}) => (
<div>
<form onSubmit={handleSubmit}>
<Grid
container
direction="column"
alignContent="center"
spacing={24}
>
<Grid item xs={10} md={10} lg={10}>
<Field
name="Email"
type="email"
component={TextField}
label="Email"
/>
</Grid>
<Grid item xs={6}>
<Field
name="Password"
type="password"
component={TextField}
label="Password"
/>
</Grid>
<Grid item xs={6}>
<Button
variant="contained"
color="primary"
type="submit"
disabled={submitting
>
Login
</Button>
</Grid>
</Grid>
</form>
</PaperWrapper>
</div>
);
Authentication.propTypes = {
submitting: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
};
const AuthenticationForm = reduxForm({
form: 'AuthenticationForm',
})(Authentication);
export default AuthenticationFom;
TextFieldWrapper.jsx:
import React from 'react';
import PropTypes from 'prop-types';
import TextField from '#material-ui/core/TextField';
const TextFieldWrapper = ({
label,
type,
}) => (
<TextField
label={label}
margin="normal"
type={type}
variant="outlined"
/>
);
TextFieldWrapper.propTypes = {
label: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
};
export default TextFieldWrapper;
When I debug the application using redux-devtoolsI find that the Field's value don't change whatever I put in the TextFields.
The state is like this:
Should I add value and onChangefunction to each Field. In the documentation of Redux-form they don't add value or onChange in their FieldComponent Material-Ui Example
When using custom components like that you need to forward to the material ui TextField the input property coming from the TextFieldWrapper.
The doc of Redux Form show it in the part {...input} of renderTextField
I found the solution, I update the TextFieldWrapperComponent:
import React from 'react';
import PropTypes from 'prop-types';
import TextField from '#material-ui/core/TextField';
const TextFieldWrapper = ({
label,
type,
input,
}) => (
<TextField
label={label}
margin="normal"
type={type}
value={input.value}
onChange={input.onChange}
variant="outlined"
/>
);
TextFieldWrapper.propTypes = {
label: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
input: PropTypes.shape({}).isRequired,
};
export default TextFieldWrapper;
So I add the input property to my component in order to get props sent by redux-form.