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

Multiple forms on page #28

Closed
janmarek opened this Issue Aug 19, 2015 · 27 comments

Comments

@janmarek
Copy link

janmarek commented Aug 19, 2015

Imagine you have multiple items on page and all of them are editable. So every item has its own form. Then textual ID in reduxForm('itemEditForm') is not enough.

Is there a solution for this situation?

@janmarek

This comment has been minimized.

Copy link

janmarek commented Aug 19, 2015

I like this library a lot. Unfortunately without this I cannot use it for all forms in my app.

@janmarek

This comment has been minimized.

Copy link

janmarek commented Aug 19, 2015

My ideas about API:

createFormReducerContainer(
    'sliceName',
    id => createFormReducer(id, ['field1', 'field2'])
)

@connect((state, props) => ({form: state.sliceName.get(props.itemId)}))
@reduxForm(props => ['sliceName', props.itemId], validationFunction)
class FormComponent {}

Something like that. Maybe you can come with something better.

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Aug 19, 2015

This is an important question. I'm thinking about it, not ignoring you. 😄 💭

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Aug 19, 2015

I think that the answer is that Redux isn't very good at this yet. It would be nice to be able to asynchronously load your list of objects, and then dynamically add/merge your reducers to the store.

  fetchRecords()
    .then(records => {
      reducers = {};
      records.forEach(record => {
        reducers[record.id] = createFormReducer(`widgets.${record.id}`, ['name', 'price']);
      });
      store.addReducers({
        widgets: reducers
      }); // merges this object into the master reducer
    });

Obviously, you'd then want to clean up with some sort of store.removeReducers() when you're done. Or maybe just have a "reducer reducer" that takes the current reducer and returns a new one to use. I see that getReducer() and replaceReducer() are deprecated, so I'm not sure where it's going.

@gaearon, is dynamic reducer mutation eventually going to be supported for applications apart from hot reloading? @janmarek is not the first to request this.

@janmarek

This comment has been minimized.

Copy link

janmarek commented Aug 19, 2015

You don't need multiple reducers. Just the one you have needs to take care for multiple data instances.

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Aug 20, 2015

Sure, but redux-form, in its current state is designed to operate on a single flat object of fields. If you make that object deeper and full of other objects, both the reducer and the validation get much more complicated. I think you're right in that they could both be wrapped in some other function that handled the multiplicity and let the original reducer and validation do their one-object jobs.

This relates somewhat to #21 and #23.

@erikras erikras closed this in 544880f Aug 21, 2015

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Aug 21, 2015

Wow. So the solution resulted in a surprisingly simple API. See Editing Multiple Records.

@janmarek

This comment has been minimized.

Copy link

janmarek commented Aug 21, 2015

Thank you sir :)

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Aug 21, 2015

You can see it in action here. If it fails to load, keep hitting reload. it's a feature!

@brennancheung

This comment has been minimized.

Copy link

brennancheung commented Aug 9, 2016

Editing Multiple Records link no longer working and can't find it in the current repo so just adding a short snippet for others running into same issue.

Basically, just use formKey.

<MyCustomComponent formKey={entity.id} />

That will make the form reducer use that so you don't get collisions.

@nicogreenarry

This comment has been minimized.

Copy link
Contributor

nicogreenarry commented Aug 18, 2016

So this all works for me - but what is the name/label of one instance of a form?

I have a page that displays multiple pieces of furniture. Each piece has an 'edit' form. They're all instances of our UpdateFurnitureForm. I'm passing a unique formKey to each one, so the forms maintain data independently. The forms work exactly as intended. (Thanks for all the helpful advice above!)

Now I'd like to programmatically change the value of one of the form fields. I already have that working on another page, on a form that is a unique form (i.e., not an instance of a set of duplicate forms). I used instructions from this comment to get that working properly. See my comment in that thread for my working code.

So for the instance of one of the multiple forms, what label should I use? The formKey values I'm passing are furniture names, for example 'couch'. I've unsuccessfully tried these:

  • 'UpdateFurnitureForm'
  • 'UpdateFurnitureForm.couch'
  • 'UpdateFurnitureForm["couch"]'
  • 'UpdateFurnitureForm[couch]'
  • 'couch'
@steakchaser

This comment has been minimized.

Copy link

steakchaser commented Sep 29, 2016

Here's the reference to the multiple records example in the v5.x docs. Anyone know how / if to support this in v6?

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Sep 29, 2016

@steakchaser The way you do that in v6 is to change the form prop passed to your decorated component. Note: The form config parameter can be passed as a prop.

<MyRecord form={`record[${record.id}]}/>
@jrutter

This comment has been minimized.

Copy link

jrutter commented Nov 9, 2016

Hi @erikras - Im trying to setup dynamic form names, do I need to setup the formName in any special manner if Im using the code from above?

<MyRecord form={record[${record.id}]}/>`

const form = reduxForm({
  form: props.form
  // validate
});

Do I need to map props?

@viotti

This comment has been minimized.

Copy link

viotti commented Nov 19, 2016

@jrutter maybe this helps: http://stackoverflow.com/a/37464048/2619107.

@brianium

This comment has been minimized.

Copy link

brianium commented Jan 4, 2017

How would this work with formValueSelector since it requires the name to match?

@cashkows-jason

This comment has been minimized.

Copy link

cashkows-jason commented Jan 27, 2017

Hi @brianium did you find a solution to this as I have the same problem?

@brianium

This comment has been minimized.

Copy link

brianium commented Jan 27, 2017

@cashkows-jason my solution was to create a container that could pass a selector function or selected values themselves. It is a little clunky in that it requires a container where you may otherwise not have one, but other than that it worked nicely

@jaspersorrio

This comment has been minimized.

Copy link

jaspersorrio commented Apr 2, 2017

Hey guys may I ask where is this documented in v6?

I tried modeling after http://redux-form.com/5.3.3/#/examples/multirecord?_k=kcy5ns and found out that version 6 doesn't require a fields option as mentioned in this SO post http://stackoverflow.com/a/37464048/2619107.

@ihorml

This comment has been minimized.

Copy link

ihorml commented Apr 17, 2017

@erikras please help, how can we use your solution with formValueSelector ?

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Apr 17, 2017

@ihorml Is your question how to know what the form name is? Seems like you could pass ownProps of true to your connect() call to know the form name.

If that is not your question, please restate it.

@ihorml

This comment has been minimized.

Copy link

ihorml commented Apr 17, 2017

@erikras I want to use multiple forms like selectingFormValues on the same page. I'm going to pass form name using props like this:

forms.map((form) => {
  <SelectingFormValuesForm form={form.id} />
  <SelectingFormValuesForm form={form.id} />
})

But how should I pass form name to this line of your example:
const selector = formValueSelector('selectingFormValues') // <-- same as form name

If I don't know the form name?

The problem is I should have the few same forms on the same page and I should get their values on any update (not on submit).

I see you have values:Object on Instance API section and I'm trying to get form values using this.props.values but it's undefined.

@anhtran1906

This comment has been minimized.

Copy link

anhtran1906 commented May 24, 2017

I used redux-form v6 and followed this instruction: http://stackoverflow.com/a/37464048/2619107 and it worked

@StevenXL

This comment has been minimized.

Copy link

StevenXL commented Aug 21, 2017

@anhtran1906 that worked seamlessly for me too. Thanks for the link.

@willshu2049

This comment has been minimized.

Copy link

willshu2049 commented Sep 6, 2017

It's a somewhat more complicated problem than issue #28. I want to have multiple forms in one component. In the parent component, I use form={...} and initialVlaues={...} to pass in initial values for each form. It works as the solutions in the above posts suggested.

However, this works only if I do not connect the child component to redux store. For example, the code below works:

//in the parent component, render the child component
<CommentModal commentId={comment.id} form={`EditCommentForm_${comment.id}`} initialValues={comment}/>

//In the child component file, export the child component. mapStateToProps is null.
...
export default connect(
  // mapStateToProps
  null,
  // mapDispatchToProps
  { addComment, editComment, openModal, closeModal }
)(reduxForm({
    validate,
    enableReinitialize: true,
  })(CommentModal)
);

However, if I am trying to connect the component to redux store, like below:

export default connect(
  // mapStateToProps
  ({modalState})=>({modalState}),
  // mapDispatchToProps
  { addComment, editComment, openModal, closeModal }
)(reduxForm({
    validate,
    enableReinitialize: true,
  })(CommentModal)
);

It stops working. The initial values for all forms will be the initial values of the last form. I tried using ownProps, without luck:

function mapStateToProps({modalState}, ownProps ){
  console.log(ownProps)
  return {
    modalState,
    initialValues: ownProps.initialValues
  }
}

Can anyone suggest what the problem is, or a solution?

@cantuket

This comment has been minimized.

Copy link

cantuket commented Sep 13, 2017

@willshu2049

2 things that may help you...

Bug In redux Form (I think?)
You need to add a random string to the reduxForm() 'form' parameter in the options to prevent this error...

Warning: Failed prop type: The prop `form` is marked as required in 'Form(EditListing)', but its value is 'undefined'.

which appears to be a bug, but not sure if anyone else is experiencing this (redufx-form v7.0.4) Any thoughts @erikras? Solution hack...

reduxFrom({
     form:'random string here',
     validate,
    enableReinitialize: true,
}) 

Dynamic generated fields
I can't tell if this pertains to you, but I'm generating my fields dynamically from the same objects that I'm setting my initialValues. To do this I set set the initialValues={item} in the decorator component (your <CommentModal>) and as I map over these I pass down the same object to the child component to generate my fields item={item} then access it with ownProps within that child...

In Parent (EditListing.js)

return _.map(this.props.listing.listing.items, item => {
          return (
            <ItemInfo form={`editItemInfo_${item._id}`} initialValues={item} key={item._id} item={item} /> 
          );
      });

In Child (ItemInfo.js)

function mapStateToProps(state, ownProps) {
  console.log(ownProps);
  return {  
    theItem:ownProps.item
  };
}

I posted a more detailed description in Stackoverflow....
https://stackoverflow.com/questions/46182829/redux-form-form-and-initialvalues-properties-not-recognized-with-m/46203814#46203814

And here is my complete implementation...

Parent (EditListing.js)

import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import ListingInfo from './ListingInfo';
import ItemInfo from './items/ItemInfo';
import AddItem from './items/AddItem';
import * as actions from '../../actions';
import {Col, Row} from 'react-grid-system';

class EditListing extends Component {

  componentDidMount () {  
      const listingId = this.props.match.params.listingId;
      this.props.fetchSingleListing(listingId);
  }

  renderItemForms() {
    if (this.props.listing.listing !== undefined) {
      return _.map(this.props.listing.listing.items, item => {
          return (
            <ItemInfo form={`editItemInfo_${item._id}`} initialValues={item} key={item._id} item={item} /> 
          );
      });
    }
  }

  render() {
    return (
      <div>
        <Row>
          <Col md={6}>
            <ListingInfo/>
          </Col>
          <Col md={6}>  
            <AddItem/>
          </Col>
        </Row>
          <Row>
            <Col md={12}>
              <h3>Current Items</h3>
              <div className="row">
              {this.renderItemForms()}
             </div>
            </Col>
          </Row>
      </div>
    );
  }
}

function mapStateToProps({listing}) {
  return {listing};
}

EditListing = reduxForm({
  form: 'none',
  fields: ["text"],
  enableReinitialize: true,
})(EditListing)

EditListing = connect(mapStateToProps,actions)(EditListing)

export default EditListing

Child (ItemInfo.js)

import _ from 'lodash';
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import * as actions from '../../../actions';
import {Col} from 'react-grid-system';
import RaisedButton from 'material-ui/RaisedButton';


class ItemInfo extends Component {

  renderSingleItem(item){
    let theItem =  _.map(_.omit(this.props.theItem, '_id'), (value,field) => {
        return (
          <div key={field}>
            <label>{field}</label>
            <Field component="input" type="text" name={field} style={{ marginBottom: '5px' }} />
            <div className="red-text" style={{ marginBottom: '20px' }}>
            </div>
          </div>
        );
      });
    return theItem || <div></div>;
  }

  render() {      
    return (
        <Col key={this.props.theItem._id} md={3}>
          <form>
            {this.renderSingleItem(this.props.theItem)}
            <RaisedButton secondary={true} label="Remove Item"/>
            <RaisedButton primary={true} label="Update Item"/>
          </form>
        </Col>
    );
  }
}


function mapStateToProps(state, ownProps) {
  console.log(ownProps);
  return {  
    theItem:ownProps.item
  };
}

ItemInfo = reduxForm({
  fields: ["text"],
  enableReinitialize: true,
})(ItemInfo)

ItemInfo = connect(mapStateToProps,actions)(ItemInfo)

export default ItemInfo

Hope this helps

@bozdoz

This comment has been minimized.

Copy link

bozdoz commented Jun 19, 2018

Not sure if there's any documentation to this, but I've found naming forms with square brackets help to separate multiple forms:

{[0,1,2,3].map(i => (
  <FormComponent form={`namedForm[${i}]`} key={i} />
)}

Now state.form.namedForm is an Array which you can iterate.

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