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

Multiple forms on page #28

Closed
janmarek opened this issue Aug 19, 2015 · 29 comments
Closed

Multiple forms on page #28

janmarek opened this issue Aug 19, 2015 · 29 comments

Comments

@janmarek
Copy link

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
Copy link
Author

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

@janmarek
Copy link
Author

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
Copy link
Member

erikras commented Aug 19, 2015

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

@erikras
Copy link
Member

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
Copy link
Author

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

@erikras
Copy link
Member

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
Copy link
Member

erikras commented Aug 21, 2015

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

@janmarek
Copy link
Author

Thank you sir :)

@erikras
Copy link
Member

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
Copy link

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
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
Copy link

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

@erikras
Copy link
Member

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
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
Copy link

viotti commented Nov 19, 2016

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

@brianium
Copy link

brianium commented Jan 4, 2017

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

@cashkows-jason
Copy link

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

@brianium
Copy link

@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
Copy link

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
Copy link

ihorml commented Apr 17, 2017

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

@erikras
Copy link
Member

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
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
Copy link

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

@StevenXL
Copy link

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

@SidecarMaster
Copy link

SidecarMaster 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
Copy link

@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
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.

@malhotraashna
Copy link

@willshu2049 did you find some solution for using redux store with redux-form in this scenario? Facing same issue.

@lock
Copy link

lock bot commented Apr 2, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Apr 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests