Redux Forms sometimes ends up with register/unregister infinite loop - reactjs

I'm having trouble taming Redux Forms.
I have the following form and fields. Which seems to work if I simply add the correct data and submit, but if I try to cause validation errors and re-enter data a few times it sometimes ends up in an infinite loop.
The infinite loop is calling action ##redux-form/REGISTER_FIELD and action ##redux-form/UNREGISTER_FIELD repeatedly and eventually results in the following error.
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Can anyone help me to understand what might be causing this and what steps I can take to get it working please?
ContactPage.js
import React, {Component} from 'react';
import ContactForm from './contact-form';
class ContactPage extends Component {
submit = values => {
console.log(values);
};
render() {
return (
<div>
<ContactForm onSubmit={this.submit}/>
</div>
);
}
}
export default ContactPage;
ContactForm.js
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import {isEmail, isRequired, maxLength} from '../validation';
import {Input, Select, TextArea} from './form-elements';
let ContactForm = ({handleSubmit}) =>
<form onSubmit={handleSubmit}>
<div>
<Field name="fullname"
label="Full Name"
component={Input}
type="text"
validate={[isRequired, maxLength(5)]}
/>
</div>
<div>
<Field name="email" component={Input} type="email"
validate={[isRequired, maxLength(254), isEmail]}
classes={['email-field']}
/>
</div>
<div>
<Field name="message" component={TextArea}
validate={[isRequired]}
/>
</div>
<button type="submit">Submit</button>
</form>
;
ContactForm = reduxForm({
form: 'contact'
})(ContactForm);
export default ContactForm;
FormElements.js
import React from 'react';
const Label = ({label, forName}) => <label htmlFor={forName} className="form-label">{label}</label>;
export const Input = ({input, label, type, classes = [], meta: {touched, error}}) => (
<div className={['form-field', 'input', ...classes].join(' ')}>
<Label label={label} forName={input.name}/>
<div>
<input {...input} type={type} placeholder={label}
className={['form-input', touched && error ? 'form-error' : ''].join(' ')}/>
{touched && error && <p className='form-error'>{error}</p>}
</div>
</div>
);
export const TextArea = ({input, label, classes = [], meta: {touched, error}}) => (
<div className={['form-field', 'text-area', ...classes].join(' ')}>
<Label label={label} forName={input.name}/>
<div>
<textarea {...input} placeholder={label} className={[touched && error ? 'form-error' : ''].join(' ')}/>
{touched && error && <p className='form-error'>{error}</p>}
</div>
</div>
);

Don't do this:
validate={[isRequired, maxLength(5)]}
every time the form is rendered maxLength(5) will construct a new function, which will cause field to rerender (because this.props.validate !== nextProps.validate)
You can use specifically defined instances of parameterized validation rules:
const maxLength = max => value =>
value && value.length > max ? `Must be ${max} characters or less` : undefined;
const maxLength15 = maxLength(15);
<Field
name="username"
type="text"
component={renderField}
label="Username"
validate={[required, maxLength15]}
/>

I had the same issue. validate={[isRequired, maxLength(5)]} didn't help. Figured out that in my component was
useEffect(() => {
dispatch(reset('myForm'));
}, [])
Helped
useEffect(() => {
return dispatch(reset('myForm'));
}, [])

Related

reduxForm validation issue in Field Component (Error: Maximum update depth exceeded)

The error message is :
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
This problem appears only when I add required function in validate attribute in Field component. Without required all works fine. I don't understand why it doesn't work...
import { Field, reduxForm } from "redux-form";
import React from "react";
import { email, minLength, required } from "../utils/formValidation";
import inputComponent from "./inputComponent";
let AuthForm = (props) => {
const { handleSubmit } = props;
const minLength8 = minLength(8);
return (
<form className='auth-form' onSubmit={handleSubmit} action='/projects'>
<Field
id='email'
name='email'
type='text'
label='Email'
component={inputComponent}
placeholder='Email'
validate={[required, email, minLength8]}
/>
<Field
id='password'
name='password'
label='Password'
component={inputComponent}
type='password'
// validate={[minLength8, required]}
/>
<button type='submit'>Sign in</button>
</form>
);
};
AuthForm = reduxForm({ form: "auth" })(AuthForm);
export default AuthForm;
functions for validation
export const email = (value) =>
value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
? "Invalid email address"
: undefined;
export const minLength = (min) => (value) =>
value && value.length < min ? `Must be ${min} characters or more` : undefined;
export const maxLength = (max) => (value) =>
value && value.length > max ? `Must be ${max} characters or less` : undefined;
export const required = (value) =>
value || typeof value === "number" ? undefined : "Required";
Input component
const inputComponent = ({
input,
label,
type,
meta: { touched, error, warning },
}) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
</div>
);
I fixed it when I wrapped parents of reduxForm components into connect() function
In my case, it was necessary to create the min length before the form, that is, after connecting the import. Don't create minLength function inside AuthForm function

Creating a Dynamic on Demand Input field

I am new to React and doing a personal project to help with some organization in my life.
I have a field where I need to be able to add multiple names some times. I think I am close...I can't get the fields to appear but they act like they are all the same field, like they are bound together
Here is what I am getting
NameInput.jsx (component)
import React, { useState } from "react";
import { Form, Label } from "semantic-ui-react";
const NameInput = ({
input,
width,
type,
placeholder,
meta: { touched, error }
}) => {
let [inputs, setInputs] = useState([""]);
return (
<div className="nameField">
<Form.Field error={touched && !!error} width={width}>
{inputs.map((value, i) => (
<div>
<label>Name {i + 1}</label>
<input {...input} placeholder={placeholder} type={type} />{" "}
{touched && error && (
<Label basic color="red">
{error}
</Label>
)}
{e =>
setInputs(
inputs.map((value, j) => {
if (i === j) value = e.target.value;
return value;
})
)
}
</div>
))}
</Form.Field>
<button
className="ui compact button"
onClick={() => setInputs(inputs.concat(""))}
>
Add Additional Seller
</button>
</div>
);
};
export default NameInput;
And this is how I call the component. This stores to my firebase as nameField
<label>Name Field</label>
<Field
name="nameField"
type="text"
component={NameInput}
placeholder="Enter Full Name"
/>
Ideally, I'd want it to save has nameField, namefield2, nameField3 but I believe I can get that part solved on my own if I could just get my component to play nice.
Haven't you ever get a warning that a key should be provided for list items?
You should assign a unique key for each input div.

Redux form cannot type

I cannot type in the text input of redux form.
it's a very minimal form
function Login({ handleSubmit, handleChange }) {
const [username, setUsername] = useState(undefined);
const [password, setPassword] = useState(undefined);
const onSubmit = (e) => {
console.log(e);
console.log(username);
console.log(password);
};
console.log(handleSubmit);
return (
<Container>
<div className={styles.centered}>
<div className={styles.form}>
<div className={styles.title}>
<H3>Login</H3>
</div>
<form onSubmit={() => handleSubmit(onSubmit)} className={styles.flexColumn}>
<div className={styles.username}>
<P>username</P>
<Field name="username" component="input" type="text" className={styles.input} />
</div>
<div className={styles.password}>
<P>password</P>
<Field name="password" component="input" type="password" className={styles.input} />
</div>
<div className={styles.downSection}>
<Flex>
<P>
Serve Aiuto?
</P>
<a href="#">
<div className={styles.contactLink}>
<P>Contattaci</P>
</div>
</a>
</Flex>
<Button type="submit" text="Accedi" />
</div>
</form>
</div>
</div>
</Container>
);
}
const mapDispatchToProps = {
login: loginAction,
};
const enhance = compose(
connect(null, mapDispatchToProps),
reduxForm({ form: 'login' }),
);
export default enhance(Login);
The handleSubmit doesn't work, i cannot console.log anything.
I tried to see the documentation and tried to search some answer on SO but i didn't find an answer.
Could you please tell me where is the error ? thanks.
So give this a try, let's leave enhance out, I don't know what it does honestly so let's try this type of Login configuration where we turn the component into a class-based one which is good practice anyway since you are receiving inputs from a user.
I do realize you are using useState which is some of the cool new features with React, but what I am recommending is to put together a less complex and conventional setup with a class-based component like so:
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
class Login extends Component {
render() {
return (
<form>
<fieldset>
<label>Email</label>
<Field
name="email"
type="text"
component="input"
/>
</fieldset>
<fieldset>
<label>Password</label>
<Field
name="password"
type="password"
component="input"
/>
</fieldset>
</form>
);
}
}
export default reduxForm({ form: "login" })(Login);
Use this to check to see if you can now type into your inputs and then start adding stuff back in and test it every single time until you find the cause of the problem.
Try first just to handle the event
<form onSubmit={onSubmit} className={styles.flexColumn}>
after that try using the this in the function onsubmit and remove the const
onSubmit(event){
console.log(e);
console.log(username);
console.log(password);
this.handleSubmit(event.target.value);
};
after several hours and a special night of bug fixing i discovered the problem:
it was in one import, exactly:
import { Field, reduxForm } from 'redux-form/immutable';
and not
import { Field, reduxForm } from 'redux-form';
this was completely unexpected, i was pretty sure that the mistake was in the body of the component, not in the import.
the structure of the file was ok.

redux-form (version 6.4.3) does not display errors

below is the code for the redux-form. Everything works beautifully with the redux store but I can not display the error messages in the span element.
import React from 'react'
import { Button } from 'react-bootstrap'
import { Field, reduxForm } from 'redux-form'
const validate = (values) => {
const errors = {}
if (!values.firstname) {
errors.firstname = 'Required'
}
return errors
}
const renderInput = (field) => (
<div>
<label>{field.placeholder}</label>
<div>
<input {...field.input}/>
{field.error && <span>{field.error}</span>}
</div>
</div>
)
#reduxForm({
form: 'addUserForm',
validate
})
export default class CreateUserForm extends React.Component {
render() {
const {addUser, handleSubmit} = this.props
return (
<form onSubmit={handleSubmit(addUser)}>
<Field type="text" placeholder="First name" component={renderInput} name="firstname" />
<Button type="submit" className="btn btn-success">Submit</Button>
</form>
)
}
}
I can clearly see that the validation function works (see screen shot below)
but there is nothing in the <span></span> element, which means the field.error has no value. I also don't get an error message at all.
Does someone know what's going on here?
Thanks,
Thomas
Your renderInput is incomplete.
The official document shows:
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
<div>
<label>{label}</label>
<input {...input} placeholder={label} type={type}/>
{
touched && (
(error && <span>{error}</span>) || (warning && <span>{warning}</span>)
)
}
</div>
)
Observe the object, parameters passed to renderField: meta: { touched, error, warning }
With that regards, shouldn't your renderInput be:
const renderInput = (field) => (
<div>
<label>{field.placeholder}</label>
<div>
<input {...field.input}/>
{field.meta.error && <span>{field.meta.error}</span>}
</div>
</div>
)
Missing => field.meta.error

Form input using Redux Form not updating

My input field is not updating on key press:
import React, { Component, PropTypes } from 'react';
import { Field, reduxForm } from 'redux-form';
class CitySelector extends Component {
render() {
const { isFetching, pristine, submitting, handleSubmit } = this.props;
return (
<form className="form-horizontal col-xs-offset-1" onSubmit={handleSubmit(this.fetchWeather)}>
<div className="form-group">
<div className="col-md-4 col-xs-4">
<Field name="city"
component={city =>
<input type="text" className="form-control" {...city.input} placeholder="enter a city for a 5 day forecast"/>
}
/>
</div>
<div className="col-md-3 col-xs-3">
<button type="submit" className="btn btn-success">Submit</button>
</div>
</div>
</form>
);
}
}
export default reduxForm({
form: 'cityForm'
})(CitySelector);
Do I need to supply an onChange handler for text inputs?
I was having the same problem and my mistake was very simple.
Here's what I had:
import { combineReducers } from 'redux';
import { reducer as forms } from 'redux-form';
import otherReducer from './otherReducer';
export default combineReducers({ otherReducer, forms });
Notice that I was importing redux-form reducer as forms and passing it as is to my combineReducers (like I did with otherReducer) using ES6 Object property value shorthand.
The problem is that the key used to pass redux-form reducer to our combineReducers MUST be named form, so we have to change it to:
export default combineReducers({ customer, form: forms });
or
import { reducer as form } from 'redux-form';
export default combineReducers({ otherReducer, form });
Hope this helps someone else...
If you are using immutable data structures, instead of:
import { reduxForm } from 'redux-form';
use this:
import { reduxForm } from 'redux-form/immutable';
See here for more info http://redux-form.com/6.7.0/examples/immutable/
I was just having an issue similar to this question, except my form wasn't submitting and my validate function also wasn't firing.
It was due to this:
I changed it from input to something else and it totally broke redux-form SILENTLY
const TextInput = ({
input, <--- DO NOT CHANGE THIS
placeholder,
type,
meta: { touched, error, warning }
}) => {
return (
<div className="row-md">
<input
placeholder={placeholder}
type={type}
className="form-text-input primary-border"
{...input} <--- OR THIS
/>
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
</div>
)
}
Here's the rest of my input if anyone wants to study it:
<Field
component={TextInput}
type="tel"
name="person_tel"
placeholder="Mobile Phone Number"
value={this.props.person_tel}
onChange={(value) => this.props.updateField({ prop: 'person_tel', value })}
/>
If you supply a custom input component to the Field, then yes you have to call onChange passed within input prop to your component. In fact, you almost got it right by spreading city.input, but there's a catch.
When you define a stateless component (or just any function) inside render() method, it is recreated upon every render. And because this stateless component is passed as a prop (component) to Field, it forces Field to render after each recreation. So, your input is going to lose focus whenever CitySelector component renders, thus, no key presses will be captured.
You should extract your stateless component into a separate definition:
const myInput = ({ input }) => (
<input type="text" className="form-control" {...input} placeholder="enter a city for a 5 day forecast" />
);
class CitySelector extends Component {
render() {
const { isFetching, pristine, submitting, handleSubmit } = this.props;
return (
<form className="form-horizontal col-xs-offset-1" onSubmit={handleSubmit(this.fetchWeather)}>
<div className="form-group">
<div className="col-md-4 col-xs-4">
<Field name="city" component={myInput} />
</div>
<div className="col-md-3 col-xs-3">
<button type="submit" className="btn btn-success">Submit</button>
</div>
</div>
</form>
);
}
}
It also makes your code more legible.
You can find more info on that problem in official docs of Redux Form. Note that you could probably use the default input without creating your own, take a look at simple form example.
I found out/ my problem was my
form: formReducer
was not in rootReducer.
formReducer must be on top. My case:
const rootReducer = combineReducers({
general: generalReducer,
data: combineReducers({
user:userReducer,
todoReducer
}),
form: formReducer
});

Resources