Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic form names - Redux form state not updating when changing forms #2886

Open
TAGC opened this issue May 4, 2017 · 11 comments
Open

Dynamic form names - Redux form state not updating when changing forms #2886

TAGC opened this issue May 4, 2017 · 11 comments

Comments

@TAGC
Copy link

TAGC commented May 4, 2017

Are you submitting a bug report or a feature request?

Bug report

What is the current behavior?

I have an application that renders pages, each of which contain forms with dynamically-configured names (by passing the form prop to the Redux form component).

When rendering a page that contains a Redux form for the first time, it successfully initialises the form and stores the form state within the state tree using @@redux-form/INITIALIZE.

When opening subsequent pages (by navigating to them using React Router), the @@redux-form/INITIALIZE action that is generated consistently contains the first form name despite the fact that the new form name is being provided as a prop to the Redux form when the page renders.

The form on the new page will be blank, and will remain blank when attempting to edit any of the fields. All @@redux-form actions (FOCUS, BLUR, etc.) will be targeting the first form even though a different form is visible and being edited.

What is the expected behavior?

Whenever a page is rendered containing a form, the name specified in the form prop should correspond to the meta.form property of the generated @@redux-form/INITIALIZE action, and the form should load containing the data passed in as initialValues. It should be possible to interact the form, and all interactions should trigger Redux Form actions that identify the currently loaded form, and not the first form that was opened after application startup/page refresh.

What's your environment?

  • Redux Form 6.6.3
  • Windows 7 64-bit
  • Electron 1.6.8
    • Node: >=7.4.0
    • Chromium: >=56.0.2924.87
    • V8: >=5.6.326.50

Other information

The "page" or "view" that gets loaded when navigating is determined by this React component:

export default function MessageDefinitionRouter (props: IMessageDefinitionRouterProps) {
  const {definition, definitionName} = props;

  if (isTxDefinition(definition)) {
    return <TxMessageDefinitionView {...props}
                                    definition={definition}
                                    form={definitionName}
                                    initialValues={definition}/>;
  } else if (isRxDefinition(definition)) {
    return <RxMessageDefinitionView {...props}
                                    definition={definition}
                                    form={definitionName}
                                    initialValues={definition}/>;
  } else {
    throw new TypeError(`Invalid definition: ${definition}`);
  }
}

Here is the code for TxMessageDefinitionView (the one for RxMessageDefinitionView is very similar):

function TxMessageDefinitionView (props: ITxMessageDefinitionViewProps) {
  const {handleSubmit, form} = props as any;
  console.warn(`Loaded definition view with form name: ${form}`);
  return <CoreMessageDefinitionView definitionName={props.definitionName}
                                    fields={getNamedFields(props.definition)}
                                    propertyForms={createTxPropertyForm(props.definition, handleSubmit)}/>;
}

export default reduxForm({enableReinitialize: false, destroyOnUnmount: false})(TxMessageDefinitionView);

On application startup, I select a page containing a form for editing one particular object, with the form named after that object. It loads fine:

select_msg1a0

I then try selecting another page, expecting to see the form populated with the initial values for that object and allowing me to edit it. Instead I see this:

select_msg1ff

Notice that the INITIALIZE action generated when rendering this page contains the original (i.e. wrong) form name (but the correct initial values payload).

@TAGC
Copy link
Author

TAGC commented May 5, 2017

One other thing in case it makes a difference - navigating to a new definition will reuse the same instance of TxMessageDefinitionView i.e. componentWillReceiveProps is triggered to pass in the new props (form, etc.), rather than a remount being triggered.

@TAGC
Copy link
Author

TAGC commented May 5, 2017

Okay, I think I have a clearer understanding of why this issue happens now. It's because mapDispatchToProps for redux-form is only invoked when the component is mounted, which means that the initialize prop passed to the redux form is only bound once - and therefore will always be configured to dispatch INITIALIZE using the initial form name (as suggested by the props parameter being called initialProps):

// reduxForm.js
   // ...
          (dispatch, initialProps) => {
            const bindForm = actionCreator => actionCreator.bind(null, initialProps.form)

            // Bind the first parameter on `props.form`
            const boundFormACs = mapValues(formActions, bindForm)

Ordinarily, all of the code in mapDispatchToProps would be re-evaluated when the props change, but instead this function is returning a closure that will always return the same bindings to enable per-instance memoization:

// reduxForm.js
return () => computedActions

That extra () => before computedActions is therefore almost certainly the cause of the issue I'm currently facing.

@TAGC
Copy link
Author

TAGC commented May 5, 2017

I've managed to resolve this issue by setting the key prop equal to my definition name that I pass to my redux-form-wrapped component:

  const txProps = {
    definitionName: props.definitionName,
    initialValues: definition,
    form: definitionName,
    key: definitionName // Added this
  };

  if (isTxDefinition(definition)) {
    return <TxMessageDefinitionView {...txProps} pristineDefinition={definition}/>;
  }
  // ...

Setting this means that React will destroy and recreate the redux form each time I navigate to a new definition.

Before this issue is closed, it could be a good idea to add something about this dispatch memoization happening somewhere in the documentation and suggest this workaround in case users need the dispatch actions to update when props change.

Edit

Alternatively, what about a prop that determines whether mapDispatchToProps returns a closure or not?

(dispatch, props) => {
  // ...
  if (props.memoizeDispatch /* true by default */) {
    return () => computedActions
  } else {
    return computedActions
  }
}

@dirkroorda
Copy link

I had the same problem, and the workaround you advised (adding a key prop with the same value as the form prop), solved it also in my case. Many thanks, because I was getting desperate. The confusing thing was that nearly everything else went OK. For example, meta.form is OK, input.value is OK. So it is really unexpected that input.onChange becomes bound to the wrong form after a few react-router navigation actions.

@alex-shamshurin
Copy link

The same issue is here. We have many user's data forms on the same page. onChange works incorrectly.

@zz-james
Copy link

confirm setting key prop as well as the form prop on the redux-form HOC made the correct form/destroy action get called and now the onChange handler has moved to the new form

@alex-shamshurin
Copy link

By the way, is this a way to use reducer.plugin with dynamic names? I did not find how. This means the only way to change specific form data is using builtin actions.

@ArjanJ
Copy link

ArjanJ commented Aug 30, 2017

Wow, so glad I found this thread. Thanks for finding a solution @TAGC !

@erikras erikras added the bug label Aug 30, 2017
@Vincz
Copy link

Vincz commented Sep 6, 2017

Thanks for the solution @TAGC ... I've been stuck with this bug for weeks ...

@eliseumds
Copy link
Contributor

You could add the key to the form directly, no? That would be safer.

@mikeyrt16
Copy link

mikeyrt16 commented Jan 18, 2019

Omg! THANK YOU @TAGC . A day of my life has been wasted on this...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants