【问题标题】:React: dynamically add input fields to formReact:动态添加输入字段到表单
【发布时间】:2016-07-30 11:45:06
【问题描述】:

我正在为表单使用formsy-react,我想在触发事件时呈现更多选项,代码如下所示:

class MultipleChoice extends Component {
constructor(props) {
    super(props);

}

render() {

    return(
        <div>
           <Form>
               <div id="dynamicInput">
                   <FormInput />
               </div>
           </Form>
        </div>
    );

}
}

我有一个按钮和 onClick 事件我想触发一个将另一个附加到 div id "dynamicInput" 的函数,这可能吗?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    这是现代动态解决方案,它通过根据 json 文件重用带有 React Hooks 的 Input 组件来工作。 这是它的外观:

    使用这种范例的好处:输入组件(具有自己的钩子状态)可以在任何其他应用程序部分中重用,而无需更改任何代码行。

    缺点要复杂得多。 这里是简化的 json(构建组件的基础):

    {
        "fields": [
            {
                "id": "titleDescription",
                "label": "Description",
                "template": [
                    {
                        "input": {
                            "required": "true",
                            "type": "text",
                            "disabled": "false",
                            "name": "Item Description",
                            "value": "",
                            "defaultValue": "a default description",
                            "placeholder": "write your initail description",
                            "pattern": "[A-Za-z]{3}"
                        }
                    }
                ]
            },
            {
                "id": "requestedDate",
                "label": "Requested Date",
                "template": [
                    {
                        "input": {
                            "type": "date",
                            "name": "Item Description",
                            "value": "10-14-2007"
                        }
                    }
                ]
            },
            {
                "id": "tieLine",
                "label": "Tie Line #",
                "template": [
                    {
                        "select": {
                            "required": true,
                            "styles": ""
                        },
                        "options": [
                            "TL625B",
                            "TL626B-$selected",
                            "TL627B",
                            "TL628B"
                        ]
                    }
                ]
            }
        ]
    }
    

    无状态输入组件,带有 Hooks,可以读取不同的输入类型,例如:文本、数字、日期、密码等。

    import React, { forwardRef } from 'react';
    
    import useInputState from '../Hooks/InputStateHolder';
    
    const Input = ({ parsedConfig, className }, ref) => {
      const inputState = useInputState(parsedConfig);
      return (
        <input
          //the reference to return to parent
          ref={ref}
          //we pass through the input attributes and rewrite the boolean attrs
          {...inputState.config.attrs}
          required={inputState.parseAttributeValue(inputState.config, 'required')}
          disabled={inputState.parseAttributeValue(inputState.config, 'disabled')}
          className={`m-1 p-1 border bd-light rounded custom-height ${className}`}
          onChange={inputState.onChange}
        />
      )
    };
    //we connect this separated component to passing ref
    export default forwardRef(Input)
    

    挂钩架 InputStateHolder.js 文件

    import { useState } from 'react';
    
    const useInputState = (initialValue) => {
      //it stores read the json, proccess it, 
      //applies modifies and stores input values
      const [config, setInputConfig] = useState({
        isLoaded: false,
        attrs: { ...initialValue }
      });
    
      //mutating and storing input values
      function changeValue(e) {
        const updatedConfig = { ...config };
        updatedConfig.attrs.value = e.target.value;
        setInputConfig({ ...config })
      }
      // to apply form configs to input element 
      //only one time at the first load
      function checkTheFirstLoad() {
        const updatedConfig = { ...config };
        if (config.attrs.value.length === 0) {
          updatedConfig.attrs.value = config.attrs.defaultValue;
          //defaultValue is not allowed to pass as attribute in React
          //so we apply its value depending on the conditions and remove it
          delete updatedConfig.attrs.defaultValue;
          updatedConfig.isLoaded = true;
          setInputConfig(updatedConfig);
        }
      }
      //parsing boolean input attributs such as required or disabled
      function parseAttributeValue(newState, attribute) {
        return typeof newState.attrs[attribute] === 'string' && newState.attrs[attribute] === 'true'
          ? true : false
      }
    
      !config.isLoaded && checkTheFirstLoad();
    
      //returning the hook storage 
      return {
        config,
        onChange: changeValue,
        parseAttributeValue
      }
    }
    
    export default useInputState;
    

    以及父 FormFields 组件(包含表单和提交标签):

    import React, { createElement } from "react";
    
    import Input from '../UI/Input';
    
    const FormField = ({ setConfig }) => {
      //it receives the parsed json and check to not be empty
      if (!!Object.keys(setConfig).length) {
        const fieldsConfig = setConfig.fields;
        //the array to get created elements in
        const fieldsToGetBuilt = [];
        // the array to store input refs for created elements
        const inputRefs = [];
        // the function to store new ref
        const setRef = (ref) => inputRefs.push(ref);
        fieldsConfig.map(field => {
          switch (true) {
            //here is we create children depending on the form configs
            case (!!field.template[0].input): {
              let classes = 'someStyle';
              fieldsToGetBuilt.push(
                createElement(Input, {
                  ref: setRef,
                  parsedConfig: field.template[0].input,
                  key: field.id,
                  className: classes
                })
              );
              break
            }
            //default case needed to build warning div notifying the missed tag
            default: {
              let classes = 'someOther danger style';
              let child = `<${Object.keys(field.template[0])[0]}/> not built`;
              fieldsToGetBuilt.push(
                createElement('div', {
                  key: field.id,
                  className: classes
                }, child)
              );
            }
          }
        })
    
        const onSubmitHandler = (e) => {
          //every time we click on submit button 
          //we receive the inputs`es values in console
          e.preventDefault();
          inputRefs.map(e =>
            console.log(e.value)
          )
        }
    
        return (
          <div className='m-2 d-flex flex-column'>
            <form onSubmit={onSubmitHandler}>
              <h5 className='text-center'>{setConfig.title}</h5>
              <div className='d-flex flex-row justify-content-center align-items-center'>
                {fieldsToGetBuilt.map(e => e)}
              </div>
              <input type="submit" onClick={onSubmitHandler} className='btn-info' />
            </form>
          </div >
        )
      } 
      // if in json there are no any fields to get built
      else return <div>no Page has been built</div>
    };
    
    export default FormField;
    

    结果在这里

    以及我们在输入字段更改并单击提交按钮后在控制台中看到的内容

    PS 在我的另一个answer 我实现了基于json 的动态模块上传

    【讨论】:

      【解决方案2】:

      是的,我们可以更新组件的底层数据(即stateprops)。 React 如此出色的原因之一是它允许我们专注于数据而不是 DOM。

      假设我们有一个要显示的输入列表(存储为state 中的字符串数组),当单击按钮时,我们会向该列表添加一个新的输入项:

      class MultipleChoice extends Component {
          constructor(props) {
              super(props);
              this.state = { inputs: ['input-0'] };
          }
      
          render() {
              return(
                  <div>
                     <Form>
                         <div id="dynamicInput">
                             {this.state.inputs.map(input => <FormInput key={input} />)}
                         </div>
                     </Form>
                     <button onClick={ () => this.appendInput() }>
                         CLICK ME TO ADD AN INPUT
                     </button>
                  </div>
              );
          }
      
          appendInput() {
              var newInput = `input-${this.state.inputs.length}`;
              this.setState(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
          }
      }
      

      显然这个例子不是很有用,但希望它能告诉你如何完成你所需要的。

      【讨论】:

      • @jayasth 如果它解决了你的问题,你可以批准这个答案。
      【解决方案3】:

      我没有使用 formsy-react,但我解决了同样的问题,在这里发布以防有人尝试在没有 formsy 的情况下做同样的事情。

      class ListOfQuestions extends Component {
        state = {
          questions: ['hello']
        }
      
        handleText = i => e => {
          let questions = [...this.state.questions]
          questions[i] = e.target.value
          this.setState({
            questions
          })
        }
      
        handleDelete = i => e => {
          e.preventDefault()
          let questions = [
            ...this.state.questions.slice(0, i),
            ...this.state.questions.slice(i + 1)
          ]
          this.setState({
            questions
          })
        }
      
        addQuestion = e => {
          e.preventDefault()
          let questions = this.state.questions.concat([''])
          this.setState({
            questions
          })
        }
      
        render() {
          return (
            <Fragment>
              {this.state.questions.map((question, index) => (
                <span key={index}>
                  <input
                    type="text"
                    onChange={this.handleText(index)}
                    value={question}
                  />
                  <button onClick={this.handleDelete(index)}>X</button>
                </span>
              ))}
              <button onClick={this.addQuestion}>Add New Question</button>
            </Fragment>
          )
        }
      }
      

      【讨论】:

      • 感谢@josh。对于未来的用户,不要使用相同的对象来设置默认值。这里 Josh 使用 'hello' 和空白字符串来设置值。不要试图定义一个对象并赋值。
      【解决方案4】:

      下面是完整的解决方案

          var OnlineEstimate = React.createClass({
          getInitialState: function() {
              return {inputs:[0,1]};
          },
          handleSubmit: function(e) {
              e.preventDefault();
              console.log( this.refs );
              return false;
      
          },
          appendInput: function(e) {
              e.preventDefault();
              var newInput = this.state.inputs.length;
      
              this.setState({ inputs: this.state.inputs.concat(newInput)},function(){
                  return;
              });
      
              $('.online-est').next('.room-form').remove()
      
          },
          render: function() {
              var style = {
                  color: 'green'
              };
              return(
                      <div className="room-main">
                          <div className="online-est">
                              <h2 className="room-head">Room Details
                                  <button onClick={this.handleSubmit} className="rednew-btn"><i className="fa fa-plus-circle"></i> Save All</button>&nbsp;
                                  <a href="javascript:void(0);" onClick={this.appendInput} className="rednew-btn"><i className="fa fa-plus-circle"></i> Add Room</a>
                              </h2>
      
                             {this.state.inputs.map(function(item){
                                  return (
                                          <div className="room-form" key={item} id={item}>
                                              {item}
                                              <a href="" className="remove"><i className="fa fa-remove"></i></a>
                                              <ul>
                                                  <li>
                                                      <label>Name <span className="red">*</span></label>
                                                      <input type="text" ref={'name'+item} defaultValue={item} />
                                                  </li>
      
                                              </ul>
                                          </div>
                                  )
      
                             })}
                          </div>
                      </div>
      
              );
          }
         });
      

      【讨论】:

      • 有人用过吗?
      • 很高兴知道,我无法实现自己的解决方案。顺便说一句,对于更清洁的 React 解决方案,我会删除任何 JQuery 调用: $('.online-est').next('.room-form').remove()
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多