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】:是的,我们可以更新组件的底层数据(即state
或props
)。 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>
<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 反应式表单:为表单中的每个输入字段动态添加/删除输入字段