How to use react-hook-form inside class Component? - reactjs

I use the following code to create a login page with form validation:
import React from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
import { useForm } from 'react-hook-form';
class SignIn extends React.Component {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(errors);
render() {
return (
<div>
<Form onSubmit={handleSubmit(onSubmit)}>
<Label>Email : </Label>
<Input type="email" placeholder="email" name="email" ref={register({required: true, pattern: /^\S+#\S+$/i})}></Input>
<Label>Password : </Label>
<Input type="password" placeholder="password" name="password" ref={register({required: true, min: 8, maxLength: 20})}></Input>
</Form>
</div>
);
}
}
export default SignIn;
and I have a problem using react-hook-form inside the Class Component
My question, if it's possible, is: How to use the react-hook-form with Class Component without rewriting the code to the hook version?

You can't use hooks in react class components.
The class that you provide looks small and I think that you can easily rewrite it to functional component. Perhaps you don't want to, you can provide hoc with useForm hook that wraps your class component.
export const withUseFormHook = (Component) => {
return props => {
const form = useForm();
return <Component {...props} {...form} />
}
}
And in you SignIn component simply do:
export default withUseFormHook(SignIn);

The best and easiest way will be to change class Component, to a functional Component and use the useForm hook. Below you can find the example code:
import React from 'react'
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
import { useForm } from 'react-hook-form';
const SignIn = () => {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(errors);
return (
<>
<div>
<Form onSubmit={handleSubmit(onSubmit)}>
<Label>Email : </Label>
<Input type="email" placeholder="email" name="email" ref={register({required: true, pattern: /^\S+#\S+$/i})}></Input>
<Label>Password : </Label>
<Input type="password" placeholder="password" name="password" ref={register({required: true, min: 8, maxLength: 20})}></Input>
</Form>
</div>
</>
)}
export default SignIn;

From the react-hook-form documentation:
Does it work with Class Components?
No, not out of the box. If you wanted to do this, you could build a
wrapper around it and use it in your Class Component.
https://react-hook-form.com/faqs#DoesitworkwithClassComponents

After searching more and more I used this solution :
Creating a separate file with the hook version :
import React from 'react'
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
import { useForm } from 'react-hook-form';
const SignInHook = () => {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(errors);
return (
<>
<div>
<Form onSubmit={handleSubmit(onSubmit)}>
<Label>Email : </Label>
<Input type="email" placeholder="email" name="email" ref={register({required: true, pattern: /^\S+#\S+$/i})}></Input>
<Label>Password : </Label>
<Input type="password" placeholder="password" name="password" ref={register({required: true, min: 8, maxLength: 20})}></Input>
</Form>
</div>
</>
)
}
export default SignInHook
And Use it inside my Class Component:
import React from 'react';
import SignInHook from './SignInHook';
class SignIn extends React.Component {
render() {
return (
<div>
<SignInHook></SignInHook>
</div>
);
}
}
export default SignIn;

I had the same issue but at the end I could solve it like this:
import React from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import "./styles.css";
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(data) {
console.log(data);
}
render() {
const { register, handleSubmit, errors } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<label>Example</label>
<input name="example" defaultValue="test" ref={register} />
<label>ExampleRequired</label>
<input
name="exampleRequired"
ref={register({ required: true, maxLength: 10 })}
/>
{errors.exampleRequired && <p>This field is required</p>}
<input type="submit" />
</form>
);
}
}
function App(props) {
const form = useForm();
return <Login {...props} {...form} />;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is the live sandbox.

If you use the following code it works:
class SignIn extends React.Component {
...
render() {
const {
register,
formState: { errors },
handleSubmit } = (() => useForm({ mode: "onChange" }))();
return (...);
}
}
is it a good practice do it?

Related

How to reset the whole form after submitting in React-hook-form

What should I do after Submitting the form to reset/clear the field value?
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}`
You can get a reset function from useForm hook, and call it on your onSubmit function, like this:
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, reset } = useForm();
const onSubmit = data => {
console.log(data)
reset()
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}`
On reset documentation, you can check some good practices for reseting a form, like providing some default values to the inputs, for example.

Input react wrapper

I want to make a form for user data and I was asked to make a wrapper react components for each type of input. My problem is passing the handler to the input component.
This is my form:
import React from 'react';
import {addUser as addNewUser} from '../../services/index';
import {TextBox} from './TextBox'
interface AddUserProps{
}
interface AddUserState{
UserName:string,
}
export default class AddUser extends React.Component<AddUserProps, AddUserState> {
constructor(props:any) {
super(props);
this.state = {
UserName: '',
};
this.updateUserName = this.updateUserName.bind(this);
}
updateUserName(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({UserName: event.target.value});
}
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(this.state)
addNewUser(this.state)
}
render() {
return (
<div>
<form
className = "user-add-form"
onSubmit={(event) => this.handleSubmit(event)}
>
<h3>Adauga utilizatori:</h3>
<label htmlFor="userName">User Name:</label>
<TextBox name="userName" type="text" value={userName} handleChange={this.updateUserName}/>
<input type="submit" value="Add user"/>
</form>
</div>
)
}
}
export {AddUser};
And this is the input component that I made:
import React from 'react'
export function TextBox(props) {
return (
<div>
<input type={props.type}
name={props.name}
value={props.value}
onChange={(event) => props.HandleInput(event)}
/>
</div>
)
}
So what I don't know is how to pass the event handler to the input component, because as it is right now it can't find it.
It should exist in Textbox's props as handleChange as it's passed in the parent component. Does this not work?
<input
type={props.type}
name={props.name}
value={props.value}
onChange={(event) => props.handleChange(event)}
/>

Handling onSubmit outside of formik

I'm trying to extract values from a form constructed with Formik, however I can't seem to get these form values out of formik via the onSubmit call. What's the best way to get his out and handle it on a parent component?
This is the form component
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import moment from 'moment';
import Header from './Header';
moment.locale('en-gb');
const JoinForm = (props) => {
const initialValues = {
firstName: props.firstName || 'test'
};
return (
<div>
<Formik
initialValues={initialValues}
onSubmit={props.onSubmit}
// onSubmit={(values, actions) => {
// //console.log('this is direct from form',values)
// actions.setSubmitting(false);
// return values;
// }}
>
{props => {
const {
values,
touched,
isSubmitting,
handleChange,
handleSubmit,
handleReset,
handleBlur
} = props;
return (
<Form>
<label>First Name </label>
<Field
type="text"
name="firstName"
placeholder="First Name"
values={values.firstName}
onChange={handleChange}
onBlur={handleBlur}
/>
<button
type="button"
type="submit"
disabled={!!isSubmitting}
>
Submit
</button>
</Form>
);
}}
</Formik>
</div>
);
};
export default JoinForm;
This is the parent component
import JoinForm from './JoinForm';
import React from 'react';
import Header from './Header';
import { startAddAthlete } from '../actions/form';
class JoinPage extends React.Component{
onSubmit = (athlete) => {
console.log(athlete);
};
render() {
return (
<div>
<Header />
<JoinForm firstName={'King'} onSubmit={this.onSubmit} />
</div>
);
}
}
export default JoinPage
Am I doing this correctly? The purpose is to let the parent handle the submission and as the form is supposed to be re-usable for edits, etc
I think, your button is causing this issue. You've added both type="button" and type="submit" on the button. Removing the type="button" should fix this.
<button type="submit"
disabled={!!isSubmitting}>
Submit
</button>
Hope this will help.

How To Implement Google reCAPTCHA With Redux Form

I have a contact page on which I have a contact form defined like this:
import React from "react";
import { Field, reduxForm } from "redux-form";
import Recaptcha from "react-recaptcha";
const required = value => (value ? undefined : "This field is required.");
const email = value => value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? "Invalid email address." : undefined;
const renderInput = ({
input,
label,
type,
meta: { touched, error }
}) => (
<div className="form-group">
<label className="col-sm-2 control-label">{ label }</label>
<div className="col-sm-10">
{ (type == "text" || type == "email") ? <input { ...input } type={ type } /> : <textarea { ...input }></textarea> }
{ touched && ((error && <span className="contact-form-error-message">{ error }</span>)) }
</div>
</div>
);
const captcha = (props) => (
<div className="form-group">
<label className="col-sm-2 control-label"></label>
<div className="col-sm-10">
<Recaptcha render="explicit" onloadCallback={ console.log.bind(this, "reCAPTCHA loaded.") }
sitekey="XXXXXXXXXXXXXXXXX" onChange={props.input.onChange} />
</div>
</div>
);
const ContactForm = props => {
const { handleSubmit, submitting } = props
return (
<form className="form-horizontal" onSubmit={ handleSubmit }>
<Field
name="name"
type="text"
component={ renderInput }
label="Name:"
validate={ required }
/>
<Field
name="email"
type="email"
component={ renderInput }
label="Email:"
validate={ [required, email] }
/>
<Field
name="subject"
type="text"
component={ renderInput }
label="Subject:"
validate={ required }
/>
<Field
name="message"
type="textarea"
component={ renderInput }
label="Message:"
validate={ required }
/>
<Field name="recaptchacode" component={ captcha } />
<div className="form-group">
<label className="col-sm-2 control-label"></label>
<div className="col-sm-10">
<button type="submit" id="contact-form-button" disabled={ submitting }>Send</button>
</div>
</div>
</form>
)
}
export default reduxForm({
form: "ContactForm"
})(ContactForm);
The problem is I cannot seem to get the recaptchacode field in the values object when I click the submit button. How do I bind the value of the Recaptcha component to redux-form so that it puts it in the values object?
And since StackOverflow wants me to add more explanation to this because there's too much code, I am writing this text.
So the answer in short as I have managed to get this thing working. There are two npm packages for implementing recaptcha in react:
react-recaptcha and react-google-recaptcha. You want the second one and not the first one (which was my problem and doesn't work with redux-form) and then you want to follow this tutorial: https://github.com/erikras/redux-form/issues/1880
Hope this helps someone.
Here’s how I integrated Google ReCaptcha with React and redux-forms with Language support. Hope this will help someone.
Versions:
React: 16.5.2
react-google-recaptcha: 1.0.5
react-redux: 5.0.6
redux: 3.7.2
redux-form: 7.2.0
Redux form:
import React from 'react';
import {
reduxForm,
Field,
formValueSelector,
change,
} from 'redux-form';
import { testAction } from ‘/actions';
import { connect } from 'react-redux';
import Captcha from './Captcha';
const validate = values => {
const errors = {};
if (!values.captchaResponse) {
errors.captchaResponse = 'Please validate the captcha.';
}
return errors;
};
let TestForm = (props) => {
const {
handleSubmit,
testAction,
language, //extract language from props/or hard code it in Captcha component
} = props;
return (
<Form onSubmit={ handleSubmit(testAction)}>
<Field component={Input} name=“other_input_name” type="text" required/>
<Field dispatch={props.dispatch} component={Captcha} change={change} language={language} name="captchaResponse"/> {/* Pass redux-forms change and language to the Captcha component*/}
<Button type="submit">{‘Validate’}</Button>
</Form>
);
};
const selector = formValueSelector('testForm');
TestForm = connect(
state => ({
recaptchaValue: selector(state, 'captchaResponse'),
}),
{ testAction: testAction }
)(TestForm);
export default reduxForm({
form: ‘testForm’,
validate,
enableReinitialize: true,
})(TestForm);
Captcha component:
import React, { Component } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import styled from 'styled-components';
import { change } from 'redux-form';
class Captcha extends Component {
constructor(props) {
super(props);
window.recaptchaOptions = { lang: this.props.language }; //set language that comes from props E.g.: fr/es/en etc..
}
onChange = (value) => {
this.props.meta.dispatch(change(‘testForm’, 'captchaResponse', value));
};
render() {
const { meta: { touched, error } } = this.props;
return (
<CaptchaWrapper>
<ReCAPTCHA
sitekey={‘xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’}
onChange={response => this.onChange(response)}
/>
<ErrorMessage>{ touched ? error : '' }</ErrorMessage>
</CaptchaWrapper>
);
}
}
const CaptchaWrapper = styled.div`
`;
const ErrorMessage = styled.p`
color: red;
`;
export default Captcha;

redux-form v6: Form submission canceled because the form is not connected

I am getting this error in my console.
"Form submission canceled because the form is not connected"
after trying to migrate my redux-form from v5 to v6 since we migrated our app to a more recent version of React.
I am not sure what is going wrong here so i figured I could use a second or third pair of eyes.
Here is my "Smart Component"
import React, { PropTypes } from 'react';
import { reduxForm } from 'redux-form/immutable';
import { connect } from 'react-redux';
import { logUserIn } from '../../actions/authentication';
import { VALID_EMAIL_REGEX } from '../../config/app_config';
import LoginForm from './LoginForm';
const FORM_ID = 'loginForm';
export class LoginFormContainer extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
loginAction: PropTypes.func.isRequired,
};
performLogin = (params) => {
const { loginAction } = this.props;
const credentials = {
email: params.email,
password: params.password,
};
loginAction(credentials, '/home');
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<LoginForm
handleSubmit={ handleSubmit }
loginFunction={ this.performLogin }
submitting={ submitting }
/>
);
}
}
const validate = values => {
const errors = {};
if (!values.email || values.email === '') {
errors.email = 'Required';
}
else if (!VALID_EMAIL_REGEX.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.password || values.password === '') {
errors.password = 'Required';
}
return errors;
};
LoginFormContainer = reduxForm({
form: FORM_ID,
validate,
})(LoginFormContainer);
export default connect(null, {
loginAction: logUserIn,
})(LoginFormContainer);
I am passing down my submission handler function as a prop to my actual form that contains the Field component for inputs. The loginAction will link to the action for redux to send the values to the backend and redirect to home.
import React, { PropTypes } from 'react';
import { Field } from 'redux-form/immutable';
import { getClassName, checkButtonDisabled } from '../../utils/forms';
import { Link } from 'react-router';
const renderInput = (field) => {
return (
<div className={ getClassName(field.meta.touched, field.meta.error) }>
<input
{...field.input}
className="form-control form-control-success"
type={field.type}
/>
{field.meta.touched &&
field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
);
};
export default class LoginForm extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
loginFunction: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
};
render() {
const {
loginFunction,
submitting } = this.props;
return (
<form onSubmit={ loginFunction.bind(this) }>
<fieldset>
<div>
<label className="form-control-label">
Email address
</label>
<Field
name="email"
component={renderInput}
type="text"
placeholder="example#exampledomain.com"
/>
</div>
<div>
<label className="form-control-label">
Password
</label>
<Field
name="password"
component={renderInput}
type="password"
placeholder="your password"
/>
</div>
</fieldset>
<button
type="submit"
className="btn btn-primary"
disabled={ checkButtonDisabled(submitting) }
>
Log In
</button>
<Link to="/forgot-password">Forgot Password?</Link>
</form>
);
}
}
I successfully was able to get the form to work but when I hit login I get the error above and I am redirected to home, but I am not authenticated and I get a 422 error as well. I couldn't tell if my form connecting is the only error or if my action is not getting the right information from the form submission function.
Got any suggestions?
You are redirected home, because your loginFunction() is fired, but the form is not submitted
There are a couple of things that need to be updated. Your <form> tag must have a corresponding id and it should handle submit by passing your function to redux-form inbuilt submit handler. So you modifying LoginForm class as follows should get the form working
<form id="loginForm" onSubmit={ handleSubmit(this.loginFunction.bind(this)) } >
More about internal redux-form method handleSubmit here: http://redux-form.com/6.5.0/docs/api/Form.md/
Using the answer given to me above I just wanted to clarify what I did to solve the issue.
I grabbed the handleSubmit method that comes from the reduxForm and passed it to the LoginForm as a prop from the container that also retrieved it from the props.
I also imported the Form component from redux-form on the LoginForm component and just simply replaced the normal JSX tag with .
here were the final changes I made.
LoginForm.jsx:
//Added Form from redux-form
import { Field, Form } from 'redux-form/immutable';
render() {
const {
handleSubmit,//defined handleSubmit function from props which comes from the reduxForm being exported to the state.
loginFunction,
submitting } = this.props;
return (
//replaced <form></form> with <Form></Form>
<Form id="loginForm" onSubmit={ handleSubmit(loginFunction.bind(this)) }>
//passed my loginFunction into the handleSubmit function
//added an id to <Form> that corresponds to the forms id that was passed in reduxForm({ form: 'loginForm' })
<fieldset>
<div>
<label className="form-control-label">
Email address
</label>
<Field
name="email"
component={renderInput}
type="text"
placeholder="example#exampledomain.com"
/>
</div>
<div>
<label className="form-control-label">
Password
</label>
<Field
name="password"
component={renderInput}
type="password"
placeholder="your password"
/>
</div>
</fieldset>
<button
type="submit"
className="btn btn-primary"
disabled={ checkButtonDisabled(submitting) }
>
Log In
</button>
<Link to="/forgot-password">Forgot Password?</Link>
</Form>
);

Resources