ant-design: Form nested and array values mapPropsToFields not working

Version

2.12.6

Environment

Chrome 60.0.3112.90 OSX (irrelevant in this case)

Reproduction link

https://codepen.io/MathiasGilson/pen/jLapPz

Steps to reproduce

Try to use mapPropsToFields with an array ([{ first: {value: "nameArray"} }]) or a composed object ({ first: {value: "nestedName"}}) like in the codepen

What is expected?

This should set the corresponding inputs with the given values getFieldDecorator(‘names[0].first’) input should be set to “nameArray” getFieldDecorator(‘name.first’) input should be set to “nestedName”

What is actually happening?

the input stay empty, no error in the console to debug

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 3
  • Comments: 15 (3 by maintainers)

Most upvoted comments

It’s a known issue, I don’t have enough time to dig into it. Need time to re-design data structure and behavior of nested fields.

Sorry about that.

Here is my solution

import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input } from "antd";
import merge from "lodash/merge";
import isArray from "lodash/isArray";

const transform = obj => {
  return Object.keys(obj).reduce((acc, cv) => {
    return {
      ...acc,
      [cv]:
        typeof obj[cv] === "object" && !("value" in obj[cv])
          ? isArray(obj[cv])
            ? obj[cv].map(item => transform(item))
            : transform(obj[cv])
          : Form.createFormField({
              ...obj[cv],
              value: obj[cv].value
            })
    };
  }, {});
};

const CustomizedForm = Form.create({
  name: "global_state",
  onFieldsChange(props, changedFields) {
    props.onChange(changedFields);
  },
  mapPropsToFields(props) {
    const { user } = props;
    const transformed = transform({ user });
    return transformed;
  },
  onValuesChange(_, values) {
    console.log(values);
  }
})(props => {
  const { getFieldDecorator } = props.form;
  return (
    <Form layout="inline">
      <Form.Item label="Username">
        {getFieldDecorator("user.username", {})(<Input />)}
        {getFieldDecorator("user.password", {})(<Input />)}
        {getFieldDecorator("user.children[0].name", {})(<Input />)}
        {getFieldDecorator("user.children[1].name", {})(<Input />)}
      </Form.Item>
    </Form>
  );
});

class Demo extends React.Component {
  state = {
    fields: {
      user: {
        username: {
          value: "username"
        },
        password: {
          value: "password"
        },
        children: [
          {
            name: {
              value: "name1"
            }
          },
          {
            name: { value: "name2" }
          }
        ]
      }
    }
  };

  handleFormChange = changedFields => {
    this.setState(({ fields }) => {
      return {
        fields: merge(fields, changedFields)
      };
    });
  };

  render() {
    const fields = this.state.fields;
    return (
      <div>
        <CustomizedForm {...fields} onChange={this.handleFormChange} />
        <pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
      </div>
    );
  }
}

ReactDOM.render(<Demo />, document.getElementById("container"));

https://codesandbox.io/s/2z2ojxy22r?fontsize=14

I have more of a generic solution, but its still far from ideal. The main problem is that rc-forms expects the value field, which forces us to do a back and forth transformation if connected to a “normalized” redux store.

@connect(models => ({
  namespace: models['namespace'],
}))
@Form.create({
  mapPropsToFields: props => {
    const { data } = props.namespace
    const transform = (obj) => {
      return Object.keys(obj).reduce((acc, cv) => {
        return {
          ...acc,
          [cv]: typeof obj[cv] === 'object' ? transform(obj[cv]) : Form.createFormField({
            value: obj[cv],
          }),
        }
      }, {})
    }
    return transform(data);
  },
  onFieldsChange(props, fields) {
    console.log('onFieldsChange', fields);
    props.dispatch({
      type: 'namespace/sync',
      payload: fields, // TODO: Additional data transformation is needed otherwise we will have all the form meta data in redux
    });
  },
})