繁体   English   中英

更新和附加到 React 数组 state

[英]Updating & appending to React array state

我有一个用户创建编码问题的表单。 在表单中,可以选择通过输入和 output 文本框添加示例测试用例。 用户可以单击一个按钮来添加一个新的测试用例。 现在我有一个 state object 包含所有表单数据 formObj,其中有一个 sample_test_cases 字段,我想保存一个对象数组,例如:[{ input: "", Z78E6226:"1393D1366"1393D13666

我遇到的问题是更新这个数组。 每次添加测试用例时,我都需要能够将新的 object 连接到它。 然后在更改文本框时更新该索引处的 state。 我尝试创建一个无状态数组并对其进行更新,然后将 sample_test_cases 设置为该数组。 但是,这不起作用。

这是一个带有我的代码的沙箱: https://codesandbox.io/s/eloquent-snowflake-n4bpl?file=/src/AddProblemForm.js

如果有人能给我一些真正有用的提示。 我对 Javascript 或复杂的 state 管理不太熟悉。 谢谢。

请参阅下面的片段(我在保存沙箱时遇到问题)。 它解决的几个问题:

  1. 您还使用一些本地arr变量跟踪 state; 更新不会触发重新渲染,因此您需要修改formObj state 中的sample_test_cases数组。
  2. textarea value也应该反映 state 中的内容。 为了更方便,我将测试用例作为道具传递到SampleTestCase组件中,因此它将对 state 更改做出反应。
  3. React state 应该被视为不可变的。 因此,当输入文本框更新时,您应该将 state 设置为的 object 数组,其中包含sample_test_cases数组,该数组由第一个 i-1 测试用例、一个带有修改输入的第 i 个测试用例和剩余的测试用例构成.
  4. SampleTestCase AddProblemForm组件之外。 如果你不这样做,你会发现每当输入文本区域改变时,你就会失去键盘焦点。 这是因为SampleTestCase组件在每次渲染时都被重新定义,这是由 state 更改触发的。 (类似问题: React.js - 重新渲染时输入失去焦点
import React, { useState } from "react";
import { Form, Button, Col } from "react-bootstrap";
import { BsPlusSquare } from "react-icons/bs";

const SampleTestCase = ({ testCase, updateInput }) => {
  return (
    <Form.Row>
      <Col>
        <Form.Group controlId="input">
          <Form.Label>Sample Input</Form.Label>
          <Form.Control
            required
            as="textarea"
            rows={2}
            onChange={updateInput}
            value={testCase.input}
          />
        </Form.Group>
      </Col>
      <Col>
        <Form.Group controlId="output">
          <Form.Label>Sample Output</Form.Label>
          <Form.Control
            required
            as="textarea"
            rows={2}
            // onChange={(event) =>
            //   setFormObj({
            //     ...formObj,
            //     sample_test_cases: {
            //       ...formObj.sample_test_cases,
            //       output: event.target.value
            //     }
            //   })
            // }
          />
        </Form.Group>
      </Col>
    </Form.Row>
  );
};

const AddProblemForm = () => {
  const [formObj, setFormObj] = useState({
    sample_test_cases: [{ input: "", output: "" }]
    // other state obj info
  });

  const AddSampleTestCase = () => {
    // make new instance!
    const newTestCase = { input: "", output: "" };
    setFormObj({
      ...formObj,
      sample_test_cases: [...formObj.sample_test_cases, newTestCase]
    });
  };

  console.log(formObj);

  const updateInputFor = (i) => (event) => {
    event.preventDefault();

    const { sample_test_cases } = formObj;
    const testCase = sample_test_cases[i];
    testCase.input = event.target.value;

    setFormObj({
      ...formObj,
      sample_test_cases: [
        ...sample_test_cases.slice(0, i),
        testCase,
        ...sample_test_cases.slice(i + 1)
      ]
    });
  };

  return (
    <div>
      Problem Form
      <Form>
        <h5 style={{ paddingTop: "1rem" }}>Sample Test Cases</h5>
        {formObj.sample_test_cases.map((testCase, i) => (
          <React.Fragment key={i}>
            <SampleTestCase
              key={i}
              testCase={testCase}
              updateInput={updateInputFor(i)}
            />
            <hr />
          </React.Fragment>
        ))}
        <Button onClick={AddSampleTestCase}>
          <div>Add Sample Test Case</div>
        </Button>
      </Form>
    </div>
  );
};

export default AddProblemForm;

如果你的 state 很复杂,我建议你使用 useReducer 钩子。

挂钩 API

当您有涉及多个子值的复杂 state 逻辑或下一个 state 取决于前一个时,useReducer 通常比 useState 更可取。

AddProblemForm.js

import React, { useReducer } from "react";
import { Form, Button, Col } from "react-bootstrap";
import { BsPlusSquare } from "react-icons/bs";
import SampleTestCase from "./SampleTestCase";

const initialState = {
  sample_test_cases: [],
  counter: 0
  // other state obj info
};
function reducer(state, action) {
  switch (action.type) {
    case "addSampleTestCase": {
      const { data } = action;
      return {
        ...state,
        sample_test_cases: [...state.sample_test_cases, data],
        counter: state.counter + 1
      };
    }
    case "updateTest": {
      const { index, value } = action;
      return {
        ...state,
        sample_test_cases: state.sample_test_cases.map((item, i) => {
          if (i === index) {
            return value;
          } else {
            return item;
          }
        })
      };
    }

    default:
      throw new Error();
  }
}

const AddProblemForm = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const AddSampleTestCase = () => {
    dispatch({ type: "addSampleTestCase", data: { input: "", output: "" } });
  };

  /* console.log(state); */

  return (
    <div>
      Problem Form
      <Form>
        <h5 style={{ paddingTop: "1rem" }}>Sample Test Cases</h5>
        {state.sample_test_cases.map((sample_test_case, i) => (
          <div key={i}>
            <SampleTestCase
              sample_test_case={sample_test_case}
              updateValue={(value) =>
                dispatch({ type: "updateTest", index: i, value })
              }
            />
            <hr />
          </div>
        ))}
        <Button onClick={AddSampleTestCase}>
          <div>Add Sample Test Case</div>
        </Button>
      </Form>
    </div>
  );
};

export default AddProblemForm;

SampleTestCase.js

import React from "react";
import { Form, Button, Col } from "react-bootstrap";

const SampleTestCase = ({ sample_test_case, updateValue }) => {
  return (
    <Form.Row>
      <Col>
        <Form.Group controlId="input">
          <Form.Label>Sample Input</Form.Label>
          <Form.Control
            required
            as="textarea"
            rows={2}
            value={sample_test_case.input}
            onChange={(event) => updateValue(event.target.value)}
          />
        </Form.Group>
      </Col>
      <Col>
        <Form.Group controlId="output">
          <Form.Label>Sample Output</Form.Label>
          <Form.Control
            required
            as="textarea"
            rows={2}
            value={sample_test_case.output}
            onChange={(event) => updateValue(event.target.value)}
          />
        </Form.Group>
      </Col>
    </Form.Row>
  );
};
export default SampleTestCase;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM