【问题标题】:How to conditionally change another field in another component in react?如何在反应中有条件地更改另一个组件中的另一个字段?
【发布时间】:2021-08-03 04:03:27
【问题描述】:

我有表单组件。

class Form extends Component {
   render() {
       
        const fields = [
            {
                id: 'first-name',
                name: 'first_name',
                label: 'First Name,
                type: text,
                defaultValue: 'Nick',
                isShowing: true
            },
            {
                id: 'city',
                name: 'city',
                label: 'City',
                type: 'select',
                defaultValue: 'mithriya',
                options: [
                    {
                        value: 'georgina',
                        label: 'Georgina'
                    },

                    {
                        value: 'mithriya',
                        label: 'Mithriya'
                    }
                ],
                isShowing: false
            },
        ];

        return (
        <div className="pkg-settings">
            <form method="post">
                <table className="form-table">
                    <tbody>
                        {
                            fields.map( (field) =>

                                <Field key={field.id} attr={field} />
                            )
                        }
                    </tbody>
                </table>

                <p className="submit"><input type="submit" name="submit" id="submit" className="button button-primary button-large" value="Save Changes" /></p>

            </form>
        </div>
    )
          

表单字段分别是不同的组件:

class Field extends Component {

    constructor(props) {
        super(props);

        this.state = {
            value: props.attr.defaultValue,
            isShowing: props.attr.isShowing
        };

        this.handleInputChange = this.handleInputChange.bind(this);
    }

    handleInputChange(event) {
        this.setState(
            {
                value: event.target.value
            }
        );

        if ( 'city' === event.target.name && 'georgina' === event.target.value) {
               // I'd need to change First Name's isShowing. City's isShowing can be changed by using: this.setState.
        }
    }

   render() {

       var element = '';

        switch(this.props.attr.type) {
            case 'text':
                element = <input
                            type="text"
                            onChange={this.handleInputChange}
                            name='first_name'
                            value={this.state.value}
                        />;
            break;

case 'select':
                element = <select
                            name='city'
                            value={this.state.value}
                            onChange={this.handleInputChange}>

                            {
                                this.props.attr.options.map( (option) =>
                                    <option key={option.value} value={option.value}>{option.label}</option>
                                )
                            }

                            </select>
            break;
   
       return (
               this.state.isShowing
                ?
                 <tr valign="top">
                        <th scope="row">{this.props.attr.label}</th>
                        <td>
                             {element}
                        </td>
                    </tr>

                :

                null
                
           );
    }

有了这个,我可以更改我当前正在更改的字段的 isShowing,但我想根据当前字段的值更改另一个字段的 isShowing。

this 始终与当前字段有关。还是我做错了?所有字段都应该属于同一个类吗?

感谢您的帮助!

【问题讨论】:

  • 这里所有指出状态需要向上移动(到Form 而不是个人Field)的答案都是100% 正确的。此外,显示或不显示字段的逻辑可以是派生状态(派生自Form 组件的状态)。如果你对这个想法产生共鸣,我可以分享一个解决方案。

标签: reactjs react-component


【解决方案1】:

由于一个字段影响另一个字段的状态,因此一种方法是将isShowing 的状态移动到父组件(Form 组件)。父组件中isShowing 的状态将是一个布尔值数组,用于存储每个字段的值。您可以定义一个回调来更新Form 组件中isShowing 的状态,该状态可以传递给Field 组件。

class Form extends Component {
  state = {
    isShowing: []
  }

  updateIsShowingForField = (index, value) => {
    const isShowingClone = [...this.state.isShowing];
    isShowingClone[index] = value;

    this.setState({
      isShowing: isShowingClone
    });
  }

  render() {
    return fields.map(field, index => <Field
           key={field.id}
           attr={field}
           isShowing={this.state.isShowing[index]}
           updateIsShowingForField={this.updateIsShowingForField}
    />)
  }
}

【讨论】:

  • @micky 我已经编辑了我的答案来展示一个例子
  • @vikram-patel 感谢您的示例。但是,我仍然无法弄清楚。没有任何更新。你能查一下吗?
【解决方案2】:

由于在实现中,字段将成为彼此无权访问的子节点。孩子们需要一个共同的知识来源来维护数据。

如果事情在更大的层面上,常见的事实来源可以是 redux 存储或反应上下文。但是,在上述场景中,我们可以将父组件的状态作为真​​相的来源。

现在这是我使用功能组件以及一些优化和代码改进的实现:

Form.jsx 实现来了:

// Form.jsx

// Defining fields outside the component in order to avoid the declaration 
// and assigning of variable on each render
const FIELDS = [
    {
        id: 'first-name',
        name: 'first_name',
        label: 'First Name',
        type: 'text',
        defaultValue: 'Nick',
        isVisible: true
    },
    {
        id: 'city',
        name: 'city',
        label: 'City',
        type: 'select',
        defaultValue: 'mithriya',
        options: [
            {
                value: 'georgina',
                label: 'Georgina'
            },

            {
                value: 'mithriya',
                label: 'Mithriya'
            }
        ],
        isVisible: true
    },
];

const Form = () => {
  const [fields, setFields] = useState(FIELDS);

  const onFieldChange = (index, value) => {
    const fieldToUpdate = fields[index];
    fieldToUpdate.value = value;

    let otherFields = fields.splice(index, 1);

    // condition to update other fields
    if (fieldToUpdate.name === "city" && value === "georgina") {
      const conditionalFieldToUpdateIndex = otherFields.findIndex(
        (field) => field.name === "first_name",
      );
      const conditionalField = otherFields[conditionalFieldToUpdateIndex];
      conditionalField.isVisible = false;
      const leftOutFields = otherFields.splice(conditionalFieldToUpdateIndex, 1);
      otherFields = [...leftOutFields, { ...conditionalField }];
    }

    const fieldsToUpdate = [...otherFields, { ...fieldToUpdate }];
    setFields(fieldsToUpdate);
  };

  return (
    <div className="pkg-settings">
      <form method="post">
        <table className="form-table">
          <tbody>
            {fields.map((field, index) => (
              <Field
                key={field.id}
                field={field}
                onChange={(value) => onFieldChange(index, value)}
              />
            ))}
          </tbody>
        </table>

        <p className="submit">
          <input
            type="submit"
            name="submit"
            id="submit"
            className="button button-primary button-large"
            value="Save Changes"
          />
        </p>
      </form>
    </div>
  );
};

字段更改正在表单内处理,这使事情变得更容易也易于管理。下面是 Field.jsx 的实现:

const Field = (props) => {
  const {
    field,
    field: { name, value, type, defaultValue, isVisible },
    onChange,
  } = props;

  if (!isVisible) {
    return <React.Fragment />;
  }

  let elementToRender;

  switch (type) {
    case "select":
      elementToRender = (
        <select
          name={name}
          value={value}
          defaultValue={defaultValue}
          onChange={(e) => onChange(e.target.value)}
        >
          {field.options.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </select>
      );
      break;

    default:
      elementToRender = (
        <input
          type="text"
          onChange={(e) => onChange(e.target.value)}
          name={name}
          value={value}
          defaultValue={defaultValue}
        />
      );

      return elementToRender;
  }
};

【讨论】:

    【解决方案3】:

    我真的认为您处理表单的方式过于复杂,它只是一个表单,表单应该有不同的输入,所以是的,将所有字段放在同一个类中真的会让您遭受更少的痛苦......如果您坚持这种方式,那么第一条评论建议的是在表单组件中拥有状态并在表单中拥有 updateIsShowingForField 的方法,将其作为道具传递给所有组件并在每个输入字段中调用它在子组件中调用时会将值返回给父组件...

    如果您对如何将数据从孩子传递给父母感到困惑,请查看this article

    【讨论】:

      【解决方案4】:

      为了解决您的问题,我们必须将其分解为更小的步骤。

      1. 我们将fields 数组的值保持在Form.js 组件的状态。因此,每当我们更新字段状态时,都会导致应用程序重新渲染。 (在您的情况下,我们将根据您的情况更新某些字段的 isShowing 属性)。
       this.state = {
            fields: [
              {
                id: "first-name",
                name: "first_name",
                label: "First Name",
                type: "text",
                defaultValue: "Nick",
                isShowing: false
              },
              {
                id: "city",
                name: "city",
                label: "City",
                type: "select",
                defaultValue: "georgina",
                options: [
                  {
                    value: "georgina",
                    label: "Georgina"
                  },
      
                  {
                    value: "mithriya",
                    label: "Mithriya"
                  }
                ],
                isShowing: true
              }
            ]
          };
      
      1. 我们将在Form 组件中添加一个处理程序,例如handleFieldsChange,用于根据组合框输入值处理first_name 输入字段的可见性。该处理程序将接受两个参数,一个是输入框的name 和输入框的value。当输入字段发生变化时,将调用此处理程序。
       // Form.js
       handleFieldsChange(name, value) {
          if (name === "city" && value === "mithriya") {
            this.setState({
              fields: this.state.fields.map((field) =>
                field.id === "first-name" ? { ...field, isShowing: true } : field
              )
            });
          }
        }
      
       // Field.js
       handleInputChange(event) {
          this.setState({
            value: event.target.value
          });
      
          // here we call the handleFieldsChange handler
          this.props.handleFieldsChange(event.target.name, event.target.value); 
       }
      
      1. 从字段组件中删除 isShowing 本地状态,因为我们正在从 Form 组件本身更新此状态。因此,无需在 Fields 组件中再添加一个本地状态。

      因此,如果我们遵循所有这些步骤,我们就可以解决您的问题。我有一个代码框链接,可以在其中模拟您的案例,好的部分是一切都按您的意愿工作。?

      我还会将代码粘贴到这里,这样我们就不必移动到另一个页面了。

      Form.js

      export default class Form extends Component {
        constructor(props) {
          super(props);
          this.state = {
            fields: [
              {
                id: "first-name",
                name: "first_name",
                label: "First Name",
                type: "text",
                defaultValue: "Nick",
                isShowing: false
              },
              {
                id: "city",
                name: "city",
                label: "City",
                type: "select",
                defaultValue: "georgina",
                options: [
                  {
                    value: "georgina",
                    label: "Georgina"
                  },
      
                  {
                    value: "mithriya",
                    label: "Mithriya"
                  }
                ],
                isShowing: true
              }
            ]
          };
          this.handleFieldsChange = this.handleFieldsChange.bind(this);
        }
        handleFieldsChange(name, value) {
          if (name === "city" && value === "mithriya") {
            this.setState({
              fields: this.state.fields.map((field) =>
                field.id === "first-name" ? { ...field, isShowing: true } : field
              )
            });
          }
        }
      
        render() {
          console.log(this.state.fields);
          return (
            <div className="pkg-settings">
              <form method="post">
                <table className="form-table">
                  <tbody>
                    {this.state.fields.map((field) => (
                      <Field
                        key={field.id}
                        attr={field}
                        handleFieldsChange={this.handleFieldsChange}
                      />
                    ))}
                  </tbody>
                </table>
      
                <p className="submit">
                  <input
                    type="submit"
                    name="submit"
                    id="submit"
                    className="button button-primary button-large"
                    value="Save Changes"
                  />
                </p>
              </form>
            </div>
          );
        }
      }
      

      Field.js

      export default class Field extends Component {
        constructor(props) {
          super(props);
      
          this.state = {
            value: props.attr.defaultValue
          };
      
          this.handleInputChange = this.handleInputChange.bind(this);
        }
      
        handleInputChange(event) {
          this.setState({
            value: event.target.value
          });
      
          this.props.handleFieldsChange(event.target.name, event.target.value);
        }
      
        render() {
          var element = "";
      
          switch (this.props.attr.type) {
            case "text":
              element = (
                <input
                  type="text"
                  onChange={this.handleInputChange}
                  name="first_name"
                  value={this.state.value}
                />
              );
              break;
      
            case "select":
              element = (
                <select
                  name="city"
                  value={this.state.value}
                  onChange={this.handleInputChange}
                >
                  {this.props.attr.options.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              );
              break;
            default:
              element = <></>;
          }
          return this.props.attr.isShowing ? (
            <tr valign="top">
              <th scope="row">{this.props.attr.label}</th>
              <td>{element}</td>
            </tr>
          ) : null;
        }
      }
      
      

      【讨论】:

        【解决方案5】:

        将 updateProps 方法从父级传递给子级,如下所示

        Parent.jsx 代码

        // code to update props
        updateChildProps(childProps){
           this.setState({...state, childProps});
        }
        // render calling Child
        render() {
          return <Child {...childProps , updateProps: updateChildProps} />
        }
        

        Child.jsx 代码

        // on input change
        if(condition met){
            const newProps = {...this.props};
            newProps["fieldToChange"] = valueToChange;
            this.props.updateProps(newProps);
        }
        

        【讨论】:

          猜你喜欢
          • 2022-11-13
          • 1970-01-01
          • 2020-10-12
          • 2018-03-01
          • 2014-12-27
          • 2020-11-11
          • 2020-01-01
          • 2019-03-08
          • 2023-03-11
          相关资源
          最近更新 更多