Apparently it only happens when the component where the form is being rendered is connected to the redux store and one of the properties is being enhanced on the mapStateToProps function, for example:
const mapStateToProps = state => ({
// This causes the error
clicks: { enhanced: true, count: state.clicks.count },
// This works
// clicks: state.clicks
});
Passing a redux form component as a prop to another component is causing an infinite loop. This only happens if you pass it using an anonymous function (functional component).
Here is an example:
const NewMessageForm = reduxForm({
form: 'newMessageForm'
})(({ handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<Field
name="text"
component="input"
placeholder="Add your message here"
/>
</Form>
));
function Wrapper({ newMessageForm: NewMessageForm }) {
return (
<div className="wrapper">
<NewMessageForm />
</div>
);
}
function handleSubmit(values) {
console.log('submit', values);
}
function Counter () {
return (
<div>
<h1>Hello World!</h1>
// Here is where the problem happens
<Wrapper newMessageForm={() => <NewMessageForm onSubmit={handleSubmit} /> }/>
</div>
);
}
This error doesn't happen if the component is passed like this:
<Wrapper newMessageForm={NewMessageForm}/>
or even like this:
constructor(props) {
super(props)
this.NewMessageForm = () => <NewMessageForm onSubmit={handleSubmit} />
}
render() {
.....
<Wrapper newMessageForm={this.NewMessageForm}/>
.....
}
I have an example here
Tested on chrome using [email protected]
Stack Trace:
Uncaught RangeError: Maximum call stack size exceeded
at traverseAllChildrenImpl (traverseAllChildren.js:65)
at traverseAllChildrenImpl (traverseAllChildren.js:93)
at traverseAllChildren (traverseAllChildren.js:172)
at flattenChildren (flattenChildren.js:66)
at ReactDOMComponent._reconcilerUpdateChildren (ReactMultiChild.js:204)
at ReactDOMComponent._updateChildren (ReactMultiChild.js:312)
at ReactDOMComponent.updateChildren (ReactMultiChild.js:299)
at ReactDOMComponent._updateDOMChildren (ReactDOMComponent.js:936)
at ReactDOMComponent.updateComponent (ReactDOMComponent.js:754)
at ReactDOMComponent.receiveComponent (ReactDOMComponent.js:716)
I've tried to reproduce this bug in an unit test but, as for today, I wasn't able to.
+1
The webpack bin link is not working anymore for some reason. I've created a sample project to simulate this problem, can be accessed here (I've also updated the link on the original issue).
While I was creating the project I've found some more information about when it occurs. Apparently it only happens when the component where the form is being rendered is connected to the redux store and one of the properties is being enhanced on the mapStateToProps
function, for example:
const mapStateToProps = state => ({
// This causes the error
clicks: { enhanced: true, count: state.clicks.count },
// This works
// clicks: state.clicks
});
I also found that the infinite recursion is happening here
I did some more testing and I found what the problem really is. Basically it's an infinite dispatch loop, what happens is that the Field
component dispatches
an action to register itself during componentWillMount
.
This will trigger react redux to check what are the components that need a rerender, and because I'm always returning a new object on mapStateToProps
it will trigger a rerender, that will in turn mount the Form
and Field
components again, and this will trigger another dispatch
to register the Filed
which will result on an infinite loop.
I'm not sure whether this is a redux-form's problem or not, or if there's something we can do to prevent this error.
Maybe we can check on Field's componentWillMount
if it is already registered and not dispatch the action, since the componentWillUnmount
unregisters the component I believe this will only happens ( being register already ) when something like the problem above happens.
Or maybe we can just add a check there for recursion and raise a warning about this problem pointing to some documentation explaining it and how to avoid it.
What do you think @erikras?
I ran into this as well. I'm setting initialValues
like:
EventForm = connect(
state => ({
initialValues: {
props: [{}]
},
})
)(EventForm);
Then in a component I have render like:
render() {
return (
<div>
<FieldArray name="props" component={this.renderProps.bind(this)} />
</div>
);
}
renderProps
loops over fields:
renderProps({fields}) {
return (
<div>
{fields.map(this.renderSubProps)}
</div>);
}
renderSubProps
actually renders the Fields. Is there something wrong with this setup? If I remove initialValues
, it works, but of course, I need initial values :)
I think if you create the initialValues
outside connect
it will work.
const initialvalues = [{}];
EventForm = connect(
state => ({ initialValues })
)(EventForm);
The problem with your current approach is the same I've described in my last comment, it will cause an infinite dispatch loop.
Yeah, dispatch loop it is.
Which is kinda strange because I've been following the FieldArray example: http://redux-form.com/6.6.1/examples/fieldArrays/ It obviously works in the example :) The only significant difference that I'm aware of, is that I'm using redux-form/immutable
.
@erikras Any chance you can take a look at this?
for me it happens if I use reduxForm({ onSubmit: submit })
in decorator along with using <Form>
component.
but if I change redux-form's <Form>
component to <form>
- it works.
and <Form onSubmit={handleSubmit(submit)}>
(and no submit func in decorator) works as well.
I have this same problem. I was able to fix it by overriding the the shouldComponentUpdate
of the FormWrapper component and returning false
. I'm not sure what side effects I might introduce with this but the form is working and updating as desired.
Example:
class FormParent extends React.PureComponent {
shouldComponentUpdate(nextProps, nextState) {
return false;
}
render() {
return (
<TheForm onSubmit={data => onSaveAction(data)} />
)
}
}
with TheForm
being a redux-form.
Let me know if this helps anyone.
I had the same issue.
My form was re-rendering infinitely when the user added an object to a FieldArray.
The nextProps/this.props and nextState/this.state were all the sames in shouldComponentUpdate, but it was still re-rendering.
I was able to fix this by overriding shouldComponentUpdate
and check if nextProps/this.props and nextState/this.state were not strictly equal.
class FormParent extends React.PureComponent {
shouldComponentUpdate(nextProps, nextState) {
return JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
}
render() {
return (
<TheForm onSubmit={data => onSaveAction(data)} />
)
}
}
I was running into the same problem, and reading this thread clarified my mistake.
The component rendering the form I was using was mapping state to props like this:
export default connect(state => state)(App)
And I was able to fix "ignoring" the form's state, like this:
export default connect( ({app}) => ({app}) )(App)
What just happened is that I was recurring over the entire state, _including the form's state_, and somehow it couldn't manage well, leading to this infinite loop.
Fortunately, my wrapper component—the one rendering the redux form—doesn't rely on its state and I was able to just skip it. If that's the case of someone facing this same issue, try to just skip it as I did. Otherwise, just for a matter of statement, these shouldComponentUpdate
solutions haven't worked for me.
I've seen various errors in the past from dispatching during componentWillMount
. I don't know if it's even supported (or if anything that causes parent comps to update is). Does anyone know for sure if it should be okay, or is only dispatching during componentDidMount
the only safe option?
Gah! this keeps happening to me, I thought I'd fixed it but now its back
isSymbol.js:24 Uncaught (in promise) RangeError: Maximum call stack size exceeded
at isSymbol (isSymbol.js:24)
at toPath (toPath.js:30)
at getIn (getIn.js:18)
at getFormState (createReduxForm.js:165)
at Function.mapToProps (createReduxForm.js:729)
at mapToPropsProxy (wrapMapToProps.js:54)
at Function.detectFactoryAndVerify (wrapMapToProps.js:63)
at mapToPropsProxy (wrapMapToProps.js:54)
at handleFirstCall (selectorFactory.js:37)
at pureFinalPropsSelector (selectorFactory.js:85)
can confirm it was passing a redux-form component as a prop, not directly but as a child of the component that got passed as a prop. This was using the react router v4
<Route exact path="/" component={RubricPage} />
RubricPage contains RubricForm which is a redux-form component.
Caused : RangeError: Maximum call stack size exceeded
But only if it wasn't the first render....
re: the top comment - I haven't implemented a mapStateToProps function myself but can't say there isn't one involved in the library
also I have to say it happened on when the component was either a Class or Stateless Function.
Now I've taken the Router away it isn't happening, but that leaves me with a new problem....
changed my render method from this
render() {
const {user} = this.context.store.getState();
if(!user.UserID) {
return (
<div>
<LoginPage />
</div>
)
}
return (
<BrowserRouter>
<div>
<TabBar />
<Route exact path="/" component={RubricPage} />
</div>
</BrowserRouter>
)
}
to this
render() {
const {user} = this.context.store.getState();
return (
<BrowserRouter>
<div>
<TabBar />
{!user.UserID ? (<LoginPage />
):(
<Route exact path="/" component={RubricPage} />
)}
</div>
</BrowserRouter>
)
}
and problem has gone away. While debugging I had a console.log on the components constructor and could see it was repeatedly being called, something in redux-form and router combo was causing the component to be re initialised, not idea what the details are but things are working now.
Is this still an on-going open issue?
It might be something subtly different, but I am seeing the same issue (Maximum call stack size exceeded) that is blowing up our reduxForm
implementations when we try to use a validate
method in the reduxForm
call...
This works:
let EventForm = reduxForm({
form: 'newEvent',
onSubmit
})(FormDom)
This blows up:
let EventForm = reduxForm({
form: 'newEvent',
validate,
onSubmit
})(FormDom)
We got similar problem in our repository after a long debug.
The problem was some items of initialValues
on the object were references passed into the reduxForm
causing that during the rendering process our object mutated forcing a new re-render over an over until we ran out of stack.
After use a _.cloneDeep
, the problem is fixed, I hope that helps to people with the same problem that I had.
To add some additional context. We "happen" to be using redux-form
in the context of React
code loaded into a Rails
app that is compiling all the ES6 code to ES5 a deploy time.
Interestingly the problem had NOTHING to do with redux-form
or even our npm
packages at all... it was an outdated version of our uglifier
ruby gem. Needed to go from 2.7.1
to 3.2.0
which also upgraded the version of execjs
(again a ruby gem)...
Many days of hair pulling resulted in the discovery that something else unknown was causing a strange interaction. But hey, it's working now (for us) at least! :)
I have run into this issue twice. It happens when I introduce a closure in my mapStateToProps
.
function mapStateToProps (state, ownProps) {
const selector = closureSelector()
return (
data: selector(state, ownProps.selectArg)
)
}
The first time I just stopped using redux-form
. Now that I have run into it again, it feels to me like redux-form
is causing some sort of state side effect that causes unnecessary reevaluation. I can use a shouldComponentUpdate
function and see that my state
and props
are still equal, because the function returns the same data. It is the fact that the closure returns a function that evaluates as not equal to the previous function that breaks. I'm using selectors like this in several views to memoize data and not having issues. It is only when redux-form
is involved that it breaks. Anyone have ideas on that?
EDIT!!
I found that wrapping mapStateToProps
in a makeMapStateToProps
function is needed when using memoized selectors. This prevents a new instance of the selector from being called each time props are checked. Thus preventing infinite rendering.
const makeMapStateToProps = () => {
const selector = closureSelector()
returnfunction mapStateToProps (state, ownProps) {
return (
data: selector(state, ownProps.selectArg)
)
}
}
I am having issues with infinite loops as well. I can reproduce by trying to call this.props.initialize in componentWillMount with undefined values, without a try/catch surrounding componentWillMount. I am using React 15.5.4, redux-form 7.0.3.
const selector = formValueSelector("well-basic-information-dialog");
const mapStateToProps = state => {
return {
well: state.well,
values: {
wellCountry: selector(state, "wellCountry"),
},
};
};
...
componentWillMount() {
const {
undefinedValue,
} = this.props;
this.props.initialize({
undefinedValue
});
}
...
export default reduxForm({
form: "basic-information-dialog",
validate,
})(connect(mapStateToProps)(BasicInformationDialog));
Please look into this issue @erikras or any other redux-form maintainer. The above comment from @tiagoengel seems to be spot on for many of the cases where I've encountered this bug. They all appear to be caused by instantiating things passed to redux-form in the render method.
My team struggles with this issue every couple weeks and it is always a very lame time suck. The reason it is so bad is because there is no informative message about what might be going wrong. You usually have to dig for quite a while.
Even if there isn't a way to stop the infinite render loop, at the very least we should be able to provide informative warning messages in the console about what might be causing things to loop.
Linking some related issues here as well just to point out how common of an issue this really is:
https://github.com/erikras/redux-form/issues/2737 https://github.com/erikras/redux-form/issues/3581 https://github.com/erikras/redux-form/issues/2103
I am having this problem too, if in the same form I have a formValueSelector and field level validation (different fields).
If I take one or the other away it stops, otherwise, it goes into an infinite loop.
I still haven't been able to fix it and only now just worked out it was that combination, so I am not sure if it is possible to have both a formValueSelector (showing one field depends on the value of another) and field level validation.
Hello,
I've not checked internal code, but it's related to change of props above reduxForm HoC. The most likely it's related to Field registration and unregistration.
Just imagine this situation.
const mapStateToProps = (state) => ({
currentDateTime: new Date(), // generates new reference every time the state changes.
})
export const ComponentA = compose(
connect(mapStateToProps),
reduxForm({
form: 'bar',
fields: ['foo'],
}),
)(PureComponentA) // PureComponentA contains Field element.
so infinite loop happens due state change on given path:
state.form.bar.registeredFields.foo.count
reduxForm should not care about currentDateTime.
Please feel free to point me if I have observed things incorrectly.
Thanks.
I've found issue. validate
on Field element must point to the same reference otherwise given Field is reregistered.
Following code caused given issue with construction that I've mention in comment above .
<Field
validate = { [
(value) => {},
] }
</Field>
Hi,
I was able to get rid of the infinite register/unregister loop by placing the function that is passed to FieldArray
as component
outside the render
function of the main Component:
const renderMyArrayFields = ({ fields, meta: { touched, error } }) => (
// my array fields
)
class MyForm extends Component {
render () {
return (
<div>
<FieldArray name="myName" component={renderMyArrayFields} />
//...
Sorry, for this absolute newby remark. I know it does not help you, but it helped me and it is worth sharing. There is not a lot of non-expert information available on redux-form.
I did the same as @rostislav-simonik and put a function inline as props. Once I moved the function out of the component and referenced it - the looping craziness stopped. Thank you @rostislav-simonik !
I've found issue.
validate
on Field element must point to the same reference otherwise given Field is reregistered.
Following code caused given issue with construction that I've mention in comment above .<Field validate = { [ (value) => {}, ] } </Field>
Thanks @rostislav-simonik. Solved my problem. This side effect is happening because of the change implemented as a result of the issue https://github.com/erikras/redux-form/issues/3012, this change: https://github.com/erikras/redux-form/commit/0b578c7e9a29d1f35edc531636815c5dc21572dc#diff-ca0d4d55e50da37455f88ffb85da436b. It is a change that can break existing codes when upgrading to redux-form version 7.
Probably would be great to implement shallow equal for that prop.
Sent from my iPhone
On 20 Sep 2018, at 18:19, Lucas Villela Neder Issa notifications@github.com wrote:
I've found issue. validate on Field element must point to the same reference otherwise given Field is reregistered.
Following code caused given issue with construction that I've mention in comment above .
validate = { [
(value) => {},
] }
Thanks @rostislav-simonik. Solved my problem. This side effect is happening because of the change implemented as a result of the issue #3012, this change: 0b578c7#diff-ca0d4d55e50da37455f88ffb85da436b. It is a change that can break existing codes when upgrading to redux-form version 7.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
The current version has this change: https://github.com/erikras/redux-form/commit/b02e8a43486b875957359da55d1790d25b62ca92#diff-ca0d4d55e50da37455f88ffb85da436b, but the problem still almost the same. This last change will allow to create new array with the same functions (the same reference to the function), but if the same function is recreated (different reference) the problem still exists.
For example:
Will work fine:
const myValidation = (value) => {}; // Must be executed only once per component.
// ...
<Field
validate = { [
myValidation,
] }
</Field>
May cause "Maximum call stack size exceeded" error:
<Field
validate = { [
(value) => {},
] }
</Field>
@rostislav-simonik thank you for helping me solving my problem.
Infinite loop causes:
class AddVehicleDialog extends Component {
...
render() {
return (
...
<Field
fullWidth
autoComplete="off"
name="registrationPlate"
label="Registration plate"
validate={[required, maxLength(10)]}
component={TextField} />
...
)
}
...
}
Works fine by moving validation array outside render method, e.g.:
const validate = [required, maxLength(10)];
class AddVehicleDialog extends Component {
...
render() {
return (
...
<Field
fullWidth
autoComplete="off"
name="registrationPlate"
label="Registration plate"
validate={validate}
component={TextField} />
...
)
}
...
}
This means that dynamic validation, for example validation that confirm password field is equal to the value of password filed field, is not possible.
Hey @yourGuy, Its totally possible!
If they are part of the same form the validator function will be give all the form values in the second argument and then you can use the name to look up the value of the password field and check its equal. note you the logic will be coupled to the name field password
in this example
// types"
Validator: (value, allValues, props, name) => string | undefined
// validator:
const checkPasswordsMatch = (
value: string,
allValues: { password: string }
): string | undefined =>
value !== allValues.password
? 'PasswordsMustMatch'
: undefined;
// component
<Password name="password" validators={checkPasswordsMatch} />
<PasswordConfirm validators={checkPasswordsMatch} />
If anyone else is still having trouble with this infinite loop. You can now use the new React.Memo
to memorize the validator. This allows you to also use values that are not inside the Form values with the validator ie values from other parts of state:
import React, { useMemo } from 'react';
const Form = ({ externalValue }) => {
const memorizedValidator = useMemo(
() => (value) => ( value > externalValue ? undefined : "validation error"),
[externalValue]
);
return (
...
<Field ... validators={[memorizedValidator]} />
...
)
};
@Multimo thank you, I didn't know that filed level validators execute with allValues, even though it's right there in the documentation :(
https://github.com/erikras/redux-form/issues/4148
You can close 2629
one.
Hi,
I was able to get rid of the infinite register/unregister loop by placing the function that is passed toFieldArray
ascomponent
outside therender
function of the main Component:const renderMyArrayFields = ({ fields, meta: { touched, error } }) => ( // my array fields ) class MyForm extends Component { render () { return ( <div> <FieldArray name="myName" component={renderMyArrayFields} /> //...
Sorry, for this absolute newby remark. I know it does not help you, but it helped me and it is worth sharing. There is not a lot of non-expert information available on redux-form.
After trying every other way to solve this problem, I found this to be the simplest and optimal solution.
@tiagoengel
I did some more testing and I found what the problem really is. Basically it's an infinite dispatch loop, what happens is that the Field component dispatches an action to register itself during componentWillMount.
This will trigger react redux to check what are the components that need a rerender, and because I'm always returning a new object on mapStateToProps it will trigger a rerender, that will in turn mount the Form and Field components again, and this will trigger another dispatch to register the Filed which will result on an infinite loop.
You are right here.
<Wrapper newMessageForm={() => <NewMessageForm onSubmit={handleSubmit} /> }/>
Because of that redux store update circle and using an inline function, here you always return a new created form. That will try to register again, and the circle continues.
Use the solution you provided
<Wrapper newMessageForm={NewMessageForm}/>
or
constructor(props) {
super(props)
this.NewMessageForm = () => <NewMessageForm onSubmit={handleSubmit} />
}
render() {
.....
<Wrapper newMessageForm={this.NewMessageForm}/>
.....
}
I'm not sure whether this is a redux-form's problem or not, or if there's something we can do to prevent this error.
On the initial issue you provided I don't think this is a redux-form
problem. This is how react
works. Here is some links
https://overreacted.io/react-as-a-ui-runtime/#recursion
https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
For all other issues that were described here, please fill a new issue with a good description of the problem, or search maybe the issue already is created.
I'll close this for now, feel free to comment.
Works fine by moving validation array outside render method, e.g.:
const validate = [required, maxLength(10)]; class AddVehicleDialog extends Component { ... render() { return ( ... <Field fullWidth autoComplete="off" name="registrationPlate" label="Registration plate" validate={validate} component={TextField} /> ... ) } ... }
Still learning React. Often things like this make no sense to me.
You'll get used to it. :slightly_smiling_face:
export default reduxForm({
form: FORM_NAME,
validate,
destroyOnUnmount: false,
})(withConnect(MyComponent));
For me, destroyOnUnmount: false resolve the issue.
Most helpful comment
I've found issue.
validate
on Field element must point to the same reference otherwise given Field is reregistered.Following code caused given issue with construction that I've mention in comment above .