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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36512686/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 19:11:12  来源:igfitidea点击:

React: dynamically add input fields to form

javascriptreactjs

提问by jayasth

I am using formsy-react for the form, I want to render more options when an event is fired, code looks something like this:

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

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

}

render() {

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

}
}

I have a button and onClick event I want to fire a function that appends another into div id "dynamicInput", is it possible?

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

回答by Calvin Belden

Yes, we can update our component's underlying data (ie stateor props). One of the reasons React is so great is that it allows us to focus on our data instead of the DOM.

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

Let's pretend we have a list of inputs (stored as an array of strings in state) to display, and when a button is clicked we add a new input item to this list:

假设我们有一个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]) }));
    }
}

Obviously this example isn't very useful, but hopefully it will show you how to accomplish what you need.

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

回答by Josh Pittman

I didn't use formsy-react but I solved the same problem, posting here in case it helps someone trying to do the same without formsy.

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

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>
    )
  }
}

回答by AlexNikonov

Here is modern dynamic solution works by reusing Input component with React Hooksdepending on json file. Here is how it looks:

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

enter image description here

在此处输入图片说明

The benefitsof using such paradigm: the input component (having its own hook state) may be reused in any other app part without changing any line of the code.

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

The drawbackit's much more complicate. here is simplified json (to build Components basing on):

缺点它的更复杂。这是简化的 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"
                    ]
                }
            ]
        }
    ]
}

stateless Inputcomponent with Hooks, which may read different input typessuch as: text, number, date, password and some others.

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

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)

Hook holder InputStateHolder.js file

挂钩保持器 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;

And the parent FormFields component (containing form and submit tags):

以及父 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;

The result is here

结果在这里

enter image description hereand what we see in the console after input fields are changed and submit button is clicked

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

enter image description here

在此处输入图片说明

PS in my another answeri implemented dymanic module upload basing on json

PS 在我的另一个答案中,我实现了基于 json 的动态模块上传

回答by S Kumar

Below is the complete solution for this

以下是此问题的完整解决方案

    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>

        );
    }
   });