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 name #603

Closed
tricoder42 opened this issue Jan 28, 2016 · 24 comments
Closed

Dynamic form name #603

tricoder42 opened this issue Jan 28, 2016 · 24 comments

Comments

@tricoder42
Copy link

According to docs:

All of these configuration options may be passed into reduxForm() at "design time" or passed in as props to your component at runtime.

However, when I try to use dynamic form name based on ID (which doesn't change), I see form: undefined in redux-logger for all form related actions.

Something like:

const DynamicForm = reduxForm(
  {
    fields: ["message"]
  },
  (state, props) => ({
    form: props.id
  })
)(DynamicFormComponent);

When I pass form parameter in props (<DynamicForm form={id} />, everything works.

What do I miss?

@erikras
Copy link
Member

erikras commented Jan 28, 2016

Oooh, subtle nuance here!

When it says "or passed in as props to your component at runtime", it means "passed in as props to your decorated component", where "your decorated component" is the result of the reduxForm() decorator. By using mapStateToProps (misusing, really, as you are not referencing state), you are effectively passing the prop deeper than is allowed. If you have your id as props.id, why not also pass it in the form prop?

Did that make any sense?

@tricoder42
Copy link
Author

It's not just id, it's document.message.${id} and I wanted to keep it inside component (rather that write it again every time when I use the component).

I understand that I don't reference state, but since it's just high-order component, I don't see any problem with it.

Anyway, you're right, I can pass form directly :) I was confused, because initialValues can take values from props inside mapStateToProps, but form doesn't work the same way.

@erikras
Copy link
Member

erikras commented Jan 28, 2016

Damn, you got me there. Well played. 😄 The difference is that form has to do with connect()ing to the Redux store, and initialValues does not.

@tricoder42
Copy link
Author

Ah, right! So we have three places: Component props, config and mapStateToProps and form can be set only in first two, right? Could I send PR for docs?

@erikras
Copy link
Member

erikras commented Jan 28, 2016

Sure, if you can figure out a cogent way to state it. 😄

If you prefer to forego GH contributor cred for ease, you're welcome to write a paragraph here and I'll put it in the docs. Up to you.

Gonna close this, as I think we're on the same wavelength now.

@erikras erikras closed this as completed Jan 28, 2016
@erikras
Copy link
Member

erikras commented Jan 28, 2016

@tricoder42 Apart from the obvious Hitchhiker's reference, is your username also a reference to the tricorder? That's some serious geek points. 👍

@tricoder42
Copy link
Author

@erikras It is! Tricoder42 == Triathlon + Programmer + StarTrek reference + Tricoder at GitHub is already taken

@staab
Copy link

staab commented Aug 12, 2016

For anyone else who finds this issue and wants a solution, here's what I'm doing:

const enhance = compose(
  connect((state, props) => ({form: getFormName(state, props), fields: getFields(state, props)})),
  reduxForm())

This is sorta pseudocode, but the gist here is that you wrap your redux form wrapped component in yet another redux-connected component that just maps passed props (or state) to the props passed into the form component. This way you can have component-local logic for determining a dynamic form.

@djfm
Copy link

djfm commented Sep 3, 2016

I'm writing a basic CRUD and I need my form to adopt the entity's ID, here's what I'm doing (same idea as @staab)

const Entity = connect(
  (state, { routeParams }) => ({
    form: routeParams.entityId || 'new-entity',
  })
)(
  reduxForm()(() =>
    <form>
      <Field name="hello" type="text" component="input" />
      {' '}
      <Link to="/entities/a">a</Link>
      {' '}
      <Link to="/entities/b">b</Link>
    </form>
));

const Routes = (
  <Route component={Layout}>
    <Route path="/entities/:entityId" component={Entity} />
  </Route>
);

I can type in the form for "a":

image

But if I navigate to "b" I cannot write anything and the actions are sent to the wrong form:

image

Any idea?

@gwendall
Copy link

gwendall commented Oct 3, 2016

+1

@djfm
Copy link

djfm commented Oct 3, 2016

I think the bottom line is that React is right when they say that

image

https://facebook.github.io/react/docs/context.html

Because I had so much weird issues with redux-form I ended up writing my own project-specific form library (not that costly by the way), and realized everything becomes very weird once you start messing with the context (which is the thing that is used to propagate form name to form form fields).

@TheFullResolution
Copy link

I was looking for a way to pass just the form name and wanted to share how I used @staab solution.

I am using same component for filling in optionally address in few forms, that is why passing name of the form in props made a lot of sense:

let FormAddress = compose(connect((state, props) => ({form: props.form})), reduxForm({destroyOnUnmount: false, asyncBlurFields: []}))(ValidationForm);

@erikras
Copy link
Member

erikras commented Oct 17, 2016

The form name can be a prop to the decorated component.

chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Oct 19, 2016
- Using reduxForm() within render() causes DOM to get thrown away,
  and has weird side effects like redux-form/redux-form#1944.
  See redux-form/redux-form#603 (comment)
- Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation
- Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent)
- Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Oct 19, 2016
- Using reduxForm() within render() causes DOM to get thrown away,
  and has weird side effects like redux-form/redux-form#1944.
  See redux-form/redux-form#603 (comment)
- Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation
- Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent)
- Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Oct 20, 2016
- Using reduxForm() within render() causes DOM to get thrown away,
  and has weird side effects like redux-form/redux-form#1944.
  See redux-form/redux-form#603 (comment)
- Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation
- Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent)
- Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Oct 20, 2016
…#5524)

- Removed custom form reducers in favour of redux-form (updateField(), addForm(), etc)
- Storing 'state' in schema reducer to avoid a separate forms reducer tree (no longer needed for other use cases). This means 'state' is only the "initial state", any form edits will be handled by redux-form internally in the 'reduxForm' reducer namespace
- Removed componentWillUnmount() from <Form> since there's no more reducer state to clean up (removed formReducer.js), and redux-form handles that internally
- Removed isFetching state from <FormBuilder> since there's now a props.submitting coming from redux-form
- Improved passing of "clicked button" (submittingAction), using component state rather than reducer and passing it into action handlers (no need for components to inspect it any other way)
- Hacky duplication of this.submittingAction and this.state.submittingAction to avoid re-render of <FormBuilder> *during* a submission (see redux-form/redux-form#1944)
- Inlined handleSubmit() XHR (rather than using a redux action). There's other ways for form consumers to listen to form evens (e.g. onSubmitSuccess passed through <FormBuilder> into reduxForm()).
- Adapting checkbox/radio fields to redux-forms
  Need to send onChange event with values rather than the original event,
  see http://redux-form.com/6.1.1/docs/api/Field.md/#-input-onchange-eventorvalue-function-
- Using reduxForm() within render() causes DOM to get thrown away,
  and has weird side effects like redux-form/redux-form#1944.
  See redux-form/redux-form#603 (comment)
- Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation
- Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent)
- Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
@sgentile
Copy link

@TheFullResolution solution is very nice - I wish this was documented somewhere, I'm sure it's a fairly common question

Thanks for posting that help

@jrutter
Copy link

jrutter commented Nov 9, 2016

I'm having a real problem trying to implement the dynamic form names. I'm passing the formName down through the props, but reduxForm doesnt seem to know anything about the state or props values. How do I get around this?

@just-boris
Copy link
Contributor

Got this problem with the latest release 6.2.0. I am passing the form name dynamically, but the inner component still shows the previous state.

See the example: http://www.webpackbin.com/Vkwc8SZzM
When you are switching current form to "Form B", it is not possible to edit input value. It's happening because all redux-form actions are still bound to the previous form name. After some investigation, I have figured out, that bindDispatchToProps is not called after form change. Any ideas how to work around this?

tractorcow pushed a commit to silverstripe/silverstripe-admin that referenced this issue Mar 13, 2017
- Removed custom form reducers in favour of redux-form (updateField(), addForm(), etc)
- Storing 'state' in schema reducer to avoid a separate forms reducer tree (no longer needed for other use cases). This means 'state' is only the "initial state", any form edits will be handled by redux-form internally in the 'reduxForm' reducer namespace
- Removed componentWillUnmount() from <Form> since there's no more reducer state to clean up (removed formReducer.js), and redux-form handles that internally
- Removed isFetching state from <FormBuilder> since there's now a props.submitting coming from redux-form
- Improved passing of "clicked button" (submittingAction), using component state rather than reducer and passing it into action handlers (no need for components to inspect it any other way)
- Hacky duplication of this.submittingAction and this.state.submittingAction to avoid re-render of <FormBuilder> *during* a submission (see redux-form/redux-form#1944)
- Inlined handleSubmit() XHR (rather than using a redux action). There's other ways for form consumers to listen to form evens (e.g. onSubmitSuccess passed through <FormBuilder> into reduxForm()).
- Adapting checkbox/radio fields to redux-forms
  Need to send onChange event with values rather than the original event,
  see http://redux-form.com/6.1.1/docs/api/Field.md/#-input-onchange-eventorvalue-function-
- Using reduxForm() within render() causes DOM to get thrown away,
  and has weird side effects like redux-form/redux-form#1944.
  See redux-form/redux-form#603 (comment)
- Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation
- Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent)
- Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
@clu3
Copy link

clu3 commented Apr 19, 2017

Just to detail @TheFullResolution 's solution, this is what I did and it's been working fine.

I have a Form which is a Presentational component. And wrap it with reduxForm

// Form.js
Form = reduxForm({
  // you can add other reduxForm options here
  destroyOnUnmount: false,
})(Form);

export Form;

And then I have a FormContainer which contains all the logic, like fetching data, handle onSubmit and such, and it will have a formId, passed down as a prop from the parent, which is used as the name of the form in redux-form.
This FormContainer in my case has some mapStateToProps as well

// FormContainer
import Form from './Form'; 
....
class FormContainer extends Component 
{
  onSubmit (data, dispatch) {...}

  render() {
    return <Form {...this.props} onSubmit={onSubmit} />
  }
}

// it's important in this case to note that mapStateToProps can have a second parameter, which is the props passed down
// from the calling (parent) component
function mapStateToProps(state, props) {
  const someOtherDataFromStore = state.someOtherDataFromStore ;

  return {
    form: props.formId,
    someOtherDataFromStore,
  };
}

export default connect(mapStateToProps)(FormContainer);

And then use FormContainer like this

<FormContainer formId="my_form" />

then the redux form state will reside in state.form.my_form

@huyhoangtb
Copy link

huyhoangtb commented Apr 19, 2017

@clu3 👍 I have same solution with you, it's been working fine with me too!

@anhldbk
Copy link

anhldbk commented Apr 25, 2017

@clu3 You've made my day. Thank you!

@chrispaynter
Copy link

In order to use Redux's handleSubmit method, I needed to use the reduxForm function so that it was available on the props. FYI I've only been using redux a few days, but I adapted @clu3's answer and this seems to work for me. But is this the correct way to do this though?

/* imports omitted */

class ProviderForm extends Component {
    
    cancel(e) {
        e.preventDefault();
        this.props.cancelHandler();
    }

    render() {
        const { handleSubmit, submitting, pristine } = this.props
        return (
            <Form onSubmit={handleSubmit(this.props.submitHandler)} name="provider">
                <Field name="name" component={SemanticFormField} as={Form.Input} type="text" label="Provider Name" validate={[required]} />
                <Button disabled={submitting} onClick={event => this.cancel(event)}>Cancel</Button>
                <Button primary loading={submitting} disabled={pristine || submitting}>Sign In</Button>
            </Form>
        )
    }
}

function mapStateToProps(state, props) {
    console.log(props.formId)
    return {
        form: props.formId
    };
}

export default connect(mapStateToProps)(reduxForm({ enableReinitialize: true })(ProviderForm));

@JulianJorgensen
Copy link

Or with ES6 decorators:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Form from './Form';

@connect((state, props) => ({
  form: props.formName
}))
export default class FormContainer extends Component {
  onSubmit (data, dispatch) {
    console.log('onsubmit', data);
  }
  
  render() {
    return <Form {...this.props} onSubmit={this.onSubmit} />
  }
}
import React, { Component } from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import TextField from 'components/TextField';
import { Field, reduxForm } from 'redux-form';
import Button from 'components/Button';
import styles from './index.css';

let Form = props => {
  const { formFields, submitLabel, submitSucceeded, handleSubmit, pristine, reset, submitting } = props;
  let _responseStyles = cn(styles.response, {
    [styles.success]: submitSucceeded
  })
  return (
    <form onSubmit={handleSubmit}>
      {formFields.map((formField, i) => {
        return (
          <Field
            key={i}
            name={formField.name.toLowerCase().replace(/\s/g, '')}
            label={formField.name}
            type={formField.type || 'text'}
            component={TextField}
            required={formField.required}
            disabled={submitting}
          />  
        )
      })}
      <div>
        <Button label={submitting ? 'Sending...' : submitLabel} type='submit' disabled={submitting} />
        <div className={_responseStyles}>{submitSucceeded ? 'Message sent successfully!' : ''}</div>
      </div>
    </form>
  )
}

Form = reduxForm({
  // you can add other reduxForm options here
  destroyOnUnmount: false,
})(Form);

export default Form;

@jhonnymoreira
Copy link

jhonnymoreira commented Sep 7, 2018

I know it has been a while since the last response on the thread, but I'm using redux-form v7.4.2 and I'm using the examples given to create a Wizard with two reusable form components. For those willing to achieve the same thing, this is pretty much how I did it (one component is enough to demonstrate it):

import React from 'react'
import { connect } from 'react-redux'
import { reduxForm } from 'redux-form'

const PageOneComponent = () => {}

const PageOneForm = reduxForm({
  destroyOnUnmount: false,
  forceUnregisterOnUnmount: true,
})(PageOneComponent)

const mapStateToProps = (_, { form }) => ({
  form: form || 'leetName',
})

const PageOneFormContainer = connect(mapStateToProps)(PageOneForm)

export default PageOneFormContainer

And then, when I render the PageOneFormContainer on the Wizard component, it goes like this:

<PageOneFormContainer form="wizardForTheWin" />

This is the pretty much what you will need. Hope it helps somebody just as it helped me.

@afisher88
Copy link

I found this answer useful: https://stackoverflow.com/questions/40509754/how-do-you-pass-in-a-dynamic-form-name-in-redux-form#40688745

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';

class FormAddress extends React.Component { ... }

const mapStateToProps = (state, ownProps) => {
    return {
        form: ownProps.name,
        // other props...
    }
}

export default compose(
    connect(mapStateToProps),
    reduxForm({
        //other redux-form options...
    })
)(FormAddress);

@sailor95
Copy link

sailor95 commented Oct 8, 2019

import React from 'react'
import { connect } from 'react-redux'
import { reduxForm } from 'redux-form'

const PageOneComponent = () => {}

const PageOneForm = reduxForm({
  destroyOnUnmount: false,
  forceUnregisterOnUnmount: true,
})(PageOneComponent)

const mapStateToProps = (_, { form }) => ({
  form: form || 'leetName',
})

const PageOneFormContainer = connect(mapStateToProps)(PageOneForm)

export default PageOneFormContainer

@jhonnymoreira

I think you can directly pass a prop named form into PageOneForm without adding another layer.
(If you aren't using Redux store data in PageOneForm.)

<PageOneForm form="custom-name" />

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

No branches or pull requests