React:如何向map渲染的组件传递函数props并获取输入值数组
问题根因
你现在获取不到动态行输入值的核心原因有两个:
- 状态分层隔离:Dselect的输入值存在组件自身的state里,TableShow自己维护行列表,两层都没有把数据往最外层的FormSubmit传,表单组件自然拿不到值
- 没有做回调透传:你没有给map渲染的行组件传递「值更新时回传数据」的函数prop,子组件不知道要把数据往哪送
实现方案
核心思路是状态上提:把所有动态行的增删逻辑、行数据统一放到最外层的表单组件管理,中间的表格组件只做透传,行组件只负责渲染和触发回调,不要自己存最终要提交的输入数据。
具体实现步骤
1. 改造最外层FormSubmit组件
把原来放在TableShow里的行列表、增删行方法、行数据更新方法都挪到这里,统一维护所有表单数据:
import React, { useState } from "react"; import { Container, Button, Form, Row, Col } from "react-bootstrap"; import TableShow from "./tablePage"; const baseurl = "http://localhost:5000/api"; const FormSubmit = () => { const [inputs, setInputs] = useState({ instruct_date: "", user_name: "", }); // 统一维护动态行数据,每条数据带唯一id,和行一一对应 const [rowList, setRowList] = useState([{ id: 0, values: { pdt_code: "", pdt_category: "", pdt_name: "" } }]); const [idCount, setIdCount] = useState(0); // 普通输入框change逻辑 const changeHandler = (event) => { const name = event.target.name; const value = event.target.value; setInputs((values) => ({ ...values, [name]: value })); }; // 新增行 const addRow = () => { const newId = idCount + 1; setIdCount(newId); setRowList([...rowList, { id: newId, values: { pdt_code: "", pdt_category: "", pdt_name: "" } }]); }; // 删除行 const delRow = (rowId) => { setRowList(rowList.filter(row => row.id !== rowId)); }; // 接收子组件回传的单行更新值,这个就是要传给map渲染组件的函数类型prop const updateRowValue = (rowId, fieldName, fieldValue) => { setRowList(rowList.map(row => { if (row.id === rowId) { return { ...row, values: { ...row.values, [fieldName]: fieldValue } } } return row; })) }; // 提交逻辑 const SubmitHandler = async (event) => { event.preventDefault(); // 直接从rowList提取所有行的输入值,就是你要的rowInput数组 const rowInput = rowList.map(row => row.values); await fetch(`${baseurl}/submit/instruct`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ input1: inputs, input2: rowInput, }), }); }; return ( <Container> <h4> FORM </h4> <Form> <Row> <Col l={4} md={4} xs={6}> <div className="form-group form-inline"> <label>Date</label> <input type="date" className="form-control" name="instruct_date" value={inputs.instruct_date} onChange={changeHandler} /> </div> </Col> <Col l={4} md={4} xs={6}> <div className="form-group form-inline"> <label>Name</label> <input type="text" className="form-control" name="team_name" value={inputs.user_name} onChange={changeHandler} /> </div> </Col> </Row> {/* 把所有方法和行数据传给表格组件 */} <TableShow rowList={rowList} addRow={addRow} delRow={delRow} updateRowValue={updateRowValue} /> <Button className="my-3" onClick={SubmitHandler}> Submit </Button> </Form> </Container> ); }; export default FormSubmit;
2. 改造TableShow组件
去掉自身的状态,只做props透传,把接收到的回调函数继续传给每个map渲染的Dselect组件:
import React from "react"; import Dselect from "../components/dSelect"; import { Button } from "react-bootstrap"; const TableShow = (props) => { const { rowList, addRow, delRow, updateRowValue } = props; return ( <div> <Button className="mt-3 btn-sm btn-info" onClick={addRow}> Add rows </Button> {/* map渲染时把回调函数作为prop传给每个行组件,带上行id做区分 */} {rowList.map((row) => ( <Dselect key={row.id} r={row.id} rowValues={row.values} onDelete={delRow} onValueChange={updateRowValue} /> ))} </div> ); }; export default TableShow;
这里传函数prop没有特殊写法,和传普通prop完全一致,不管渲染多少个子组件,每个子组件触发回调时只要带上自己的唯一id,父组件就能精准更新对应行的数据。
3. 改造Dselect行组件
去掉自身存输入值的inputs状态,选择器值变化时调用父组件传进来的onValueChange回调回传数据,同时修复你原来onChange里把旧值设回去的bug:
import Select from "react-select"; import { Row, Col, Button } from "react-bootstrap"; import React, { useState, useEffect } from "react"; const baseurl = "http://localhost:5000/api"; const Dselect = (props) => { const { r, rowValues, onDelete, onValueChange } = props; const [pdtCodeOptions, setPdtCodeOptions] = useState([]); const [catListOptions, setCatListOptions] = useState([]); const [pdtListOptions, setPdtListOptions] = useState([]); // 选中值从父组件传进来的rowValues取,不再自己维护 const selectedCat = rowValues.pdt_category ? { label: rowValues.pdt_category, value: rowValues.pdt_category } : null; const selectedPcode = rowValues.pdt_code ? { label: rowValues.pdt_code, value: rowValues.pdt_code } : null; const selectedPName = rowValues.pdt_name ? { label: rowValues.pdt_name, value: rowValues.pdt_name } : null; useEffect(() => { const fetchCat = async () => { const res = await fetch(`${baseurl}/cat`); const cList = await res.json(); let catOpt = cList.map(item => ({ label: item.category, value: item.category })); setCatListOptions(catOpt); }; const fetchPdtCode = async () => { const res = await fetch(`${baseurl}/pdtlist`); const pList = await res.json(); const pdtList = pList.map(item => ({ label: item.pdt_id, value: item.pdt_id })); setPdtCodeOptions(pdtList); }; fetchCat(); fetchPdtCode(); }, []); const getPdtDetails = async (e) => { const pdtID = e.value; const res = await fetch(`${baseurl}/details`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: pdtID }), }); const detList = await res.json(); const pdt_name = detList[0].pdt_name; const category = detList[0].category; // 联动选择的值直接回传给父组件 onValueChange(r, "pdt_category", category); onValueChange(r, "pdt_name", pdt_name); }; const updateList = async (e) => { const catVal = e.value; const res = await fetch(`${baseurl}/pdtList`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: catVal }), }); const pdtList = await res.json(); const catPdtList = pdtList.map(item => ({ label: item.pdt_name, value: item.pdt_name })); setPdtListOptions(catPdtList); }; const getPdtcode = async (e) => { const pdtName = e.value; const category = rowValues.pdt_category; const data = { category: category, pdtName: pdtName }; const res = await fetch(`${baseurl}/pdtCode`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); const code = await res.json(); // 回传获取到的产品编码 onValueChange(r, "pdt_code", code); }; const changeHandler = (e, opt) => { const name = opt.name; const value = e.value; // 选中值变化时直接调用回调,传给父组件:行id、字段名、字段值 onValueChange(r, name, value); }; return ( <Row className="mt-3"> {r + 1} <Col lg={3} md={3} xs={3}> <Select name="pdt_code" options={pdtCodeOptions} placeholder="Select product Code" value={selectedPcode} onChange={(e, opt) => { changeHandler(e, opt); getPdtDetails(e); }} /> </Col> <Col lg={3} md={3} xs={3}> <Select options={catListOptions} name="pdt_category" placeholder="Category" value={selectedCat} onChange={(e, opt) => { changeHandler(e, opt); updateList(e); }} /> </Col> <Col lg={3} md={3} xs={3}> <Select name="pdt_name" options={pdtListOptions} onChange={(e, opt) => { changeHandler(e, opt); getPdtcode(e); }} value={selectedPName} placeholder="Select product Name" /> </Col> <Col lg={2} md={2} xs={2}> <Button type="button" onClick={() => onDelete(r)} className="btn btn-secondary" > Del </Button> </Col> </Row> ); }; export default Dselect;
效果说明
改完之后,不管你动态加多少行、删多少行,每个选择器的值变化都会实时同步到最外层FormSubmit的rowList状态里,提交的时候直接提取所有行的values就能得到你要的输入值数组,不会再出现拿不到值的情况。
另外你原来删除行时整段消失的问题也会一起解决,因为现在每一行的key是唯一不变的id,React不会错误复用组件状态。
内容的提问来源于stack exchange,提问作者Maya
相关产品推荐
相关产品推荐

