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.
Related
I am new to React and especially formik. I am trying to learn how to create a login form using formik that will display a label called email with the email input. A label called password with the password input and a button called submit.
My problem is that only the two inputs and submit button displays in the browser. The two labels for email and password do not display in the browser. Please advise how I can fix this.
App.js:
import React from 'react';
import './App.css';
import FormikContainer from './components/FormikContainer';
import LoginForm from './components/LoginForm';
function App() {
return (
<div>
<LoginForm />
</div>
);
}
export default App;
LoginForm.js:
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import FormikContainer from './FormikContainer';
import FormikControl from './FormikControl';
function LoginForm() {
const initialValues = {
email: '',
password: ''
};
const validationschema = Yup.object({
email: Yup.string().email('invalid email format').required('Requird'),
password: Yup.string().required('Required')
});
const onSubmit = (values) => {
console.log('Form data', values);
};
return (
<div>
<Formik
initialValues={initialValues}
validationschema={validationschema}
onSubmit={onSubmit}
>
{(formik) => {
return (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<FormikControl
control="input"
type="password"
label="Password"
name="password"
/>
<button type="submit" disabled={!formik.isValid}>
Submit
</button>
</Form>
);
}}
</Formik>
</div>
);
}
export default LoginForm;
FormikControl.js:
import React from 'react';
function FormikControl(props) {
const { control, ...rest } = props;
switch (control) {
case 'input':
return <input {...rest} />;
case 'textarea':
case 'select':
case 'radio':
case 'checkbox':
case 'date':
default:
return null;
}
return <div></div>;
}
export default FormikControl;
FormikContainer.js
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import FormikControl from './FormikControl';
function FormikContainer() {
const initialValues = {
email: ''
};
const validationschema = Yup.object({
email: Yup.string().required('Required')
});
const onSubmit = (values) => console.log('Form data', values);
return (
<div>
<Formik
initialValues={initialValues}
validationschema={validationschema}
onSubmit={onSubmit}
>
{(formik) => (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
}
export default FormikContainer;
I would like to do easily this instead :
function FormikControl({ control, id, label, ...rest }) {
return (
<>
{control === "input" && <label htmlFor={id}>{label}</label>}
<input id={id} {...rest} />
</>
);
}
export default FormikControl;
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?
How to transform this function complement to class component
:
const SyncValidationForm = (props) => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form className ="formmailforbook" onSubmit={handleSubmit(submit)}>
<Field name="email" type="email" component={renderField} label="Email"/>
<div>
<button className ="hero_button margin_left" type="submit" disabled={submitting}>Submit</button>
</div>
</form>
)
}
Pretty easy translation, you can do the same prop destructuring in the render method. Only slight change is that now it's pulling from this.props:
import React, { Component } from 'react';
export default class SyncValidationForm extends Component {
render () {
const { handleSubmit, pristine, reset, submitting } = this.props;
return (
<form className ="formmailforbook" onSubmit={handleSubmit(submit)}>
<Field name="email" type="email" component={renderField} label="Email"/>
<div>
<button className ="hero_button margin_left" type="submit" disabled={submitting}>Submit</button>
</div>
</form>
)
}
}
It's pretty much easy.. copy the code to the new file and add export defaultbefore const something like this.
export default const SyncValidationForm
Import the file as you import other components where ever you want the component and use the imported name as jsx name as usal and provide props.
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>
);
I have a form in a modal using redux-form. I have several text fields, but you can not type in them. My suspicion is that the text field doesn't get the onChange event from the redux-form but I couldn't find any clue what am I doing good.
My code is:
import React from 'react'
import { Button, Modal, Form, Message } from 'semantic-ui-react'
import { Field, reduxForm } from 'redux-form'
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => {
console.log(input)
return (
<Form.Field>
<label>{label}</label>
<input {...input} placeholder={label} type={type} />
{touched && (error && <Message error>{error}</Message>)}
</Form.Field>
)}
let AddNewModal = (props) => {
const { handleSubmit, pristine, submitting, closeNewSite, isAddNewOpen, submit } = props
return (
<Modal dimmer='blurring' open={isAddNewOpen} onClose={closeNewSite}>
<Modal.Header>Add a new site</Modal.Header>
<Modal.Content>
<Form onSubmit={handleSubmit}>
<Form.Group widths='equal'>
<Field name='domain' type='text' component={renderField} label='Domain' />
<Field name='sitemap' type='text' component={renderField} label='Sitemap URL' />
</Form.Group>
/**
* Other fields
* /
<Button type='submit' disabled={pristine || submitting}>Save</Button>
</Form>
</Modal.Content>
<Modal.Actions>
<Button color='black' onClick={closeNewSite} content='Close' />
<Button positive icon='save' labelPosition='right' onClick={submit} content='Save' disabled={pristine || submitting} />
</Modal.Actions>
</Modal>
)
}
export default reduxForm({
form: 'newsite'
})(AddNewModal)
I added the reducer and still got the same issue. At last, I found it must add the attr 'form'.
const reducers = {
routing,
form: formReducer
};
I found the problem. I forgot to inject the redux-form's reducer.
I actually had a similar issue. I will post the code that I am working on for form validation with V6 of redux-form. It works right now but the things you want to look at are componentDidMount, handleInitialize, and handleFormSubmit. Where I figured this out link.
/**
* Created by marcusjwhelan on 10/22/16.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field } from 'redux-form'; // V6 !!!!!!!!
import { createPost } from '../actions/index';
const renderInput = ({ input, label, type, meta: {touched, invalid, error }}) => (
<div className={`form-group ${touched && invalid ? 'has-danger' : ''}`}>
<label>{label}</label>
<input className="form-control" {...input} type={type}/>
<div className="text-help" style={{color: 'red'}}>
{ touched ? error : '' }
</div>
</div>
);
const renderTextarea = ({ input, label, type, meta: {touched, invalid, error }}) => (
<div className={`form-group ${touched && invalid ? 'has-danger' : ''}`}>
<label>{label}</label>
<textarea className="form-control" {...input}/>
<div className="text-help" style={{color: 'red'}}>
{ touched ? error : '' }
</div>
</div>
);
class PostsNew extends Component{
componentDidMount(){
this.handleInitialize();
}
handleInitialize(){
const initData = {
"title": '',
"categories": '',
"content": ''
};
this.props.initialize(initData);
}
handleFormSubmit(formProps){
this.props.createPost(formProps)
}
render(){
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<h3>Create A New Post</h3>
<Field
label="Title"
name="title"
type="text"
component={renderInput} />
<Field
label="Categories"
name="categories"
type="text"
component={renderInput}
/>
<Field
label="Content"
name="content"
component={renderTextarea}
/>
<button type="submit" className="btn btn-primary" >Submit</button>
</form>
);
}
}
function validate(formProps){
const errors = {};
if(!formProps.title){
errors.title = 'Enter a username';
}
if(!formProps.categories){
errors.categories = 'Enter categories';
}
if(!formProps.content){
errors.content = 'Enter content';
}
return errors;
}
const form = reduxForm({
form: 'PostsNewForm',
validate
});
export default connect(null, { createPost })(form(PostsNew));
You need to connect form reducer to your combine reducers
form: formReducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import authReducer from './authReducer';
import productsReducer from './productsReducer';
export default combineReducers({
auth: authReducer,
form: formReducer,
products: productsReducer,
});