React:动态添加输入字段到表单

Posted

技术标签:

【中文标题】React:动态添加输入字段到表单【英文标题】:React: dynamically add input fields to form 【发布时间】: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" 的函数,这可能吗?

【问题讨论】:

【参考方案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()

以上是关于React:动态添加输入字段到表单的主要内容,如果未能解决你的问题,请参考以下文章

angular 7 反应式表单:为表单中的每个输入字段动态添加/删除输入字段

动态添加/删除多个输入字段和输入行 PHP(动态表单中的动态表单)

动态添加/删除新输入到表单

在 React 中渲染动态输入字段

CakePHP:向表单添加字段(动态)

React 中的动态表单,无法读取未定义的属性“地图”