在 React 中如何使用 refs 访问映射子项的值?

ep8*_*p84 7 children parent reactjs react-component use-ref

我有一个应用程序,可以从 GraphQL 数据库中提取数据,然后将其映射到自定义表单组件(数量文本框)中。现在,组件本身保存着各自数量的状态,但我需要能够从父级访问这些值,以便我可以使用应用程序中其他地方的输入来更改数量。我已经查看了这是如何完成的,我认为这可能是我需要的,但我不知道如何应用它:[How to target DOM with React useRef in map][1]

我的应用程序由一个父元素和一个包含输入的顶部栏、一个模态组件以及从 GraphQL 查询填充的元素映射组成。

export default function Home() {
  const [batch, setBatch] = useState([]);
  const [target, setTarget] = useState("");
  const [batchCount, setBatchCount] = useState(0);
  const [cartModalStatus, setCartModalStatus] = useState(false);

  const elementValues = useRef([]);
  const fetcher = query => request("https://data.objkt.com/v2/graphql", query);
  const { data, error } = useSWR(
  `{
    listing(where: {token: {creators: {creator_address: {_eq: ` + target + `}}, supply: {_gt: "0"}}, status: {_eq: "active"}}, order_by: {token: {timestamp: asc}, price: asc}) {
      token {
        name
        display_uri
        timestamp
        supply
      }
      price
      seller {
        address
        alias
      }
      amount_left
    }
  }`, fetcher);

  const handleItemCount = () => {
    let count = 0;

    for (let i = 0; i < batch.length; i++)
      count += batch[i][1];

    setBatchCount(count);
  }

  const onCartClick = () => {
    setCartModalStatus(true);
  }

  const onHideModal = () => {
    setCartModalStatus(false);
  }

  const onSubmit = (e) => {
    console.log(e);
    setTarget(e.target[0].value);
    e.preventDefault();
  };

  const onChange = (el, quantity) => {
    let batchCopy = batch;
    let found = false;
    let foundIndex;

    for (var i = 0; i < batchCopy.length; i++)
      if (batchCopy[i][0] === el)
      {
        found = true;
        foundIndex = i;
      }       
    
    if (!found) batchCopy.push([el, quantity]);
    else if (found) batchCopy[foundIndex][1] = quantity

    setBatch(batchCopy);
    handleItemCount();

  };

  return (
    <Container>
      <TopBar onSubmit={onSubmit} cartTotal={batchCount} onCartClick={onCartClick}/>
      <CartModal show={cartModalStatus} onHideModal={onHideModal} batch={batch}/>
      <DataMap target={target} onChange={onChange} data={data} error={error}/>
    </Container>
  )
}
Run Code Online (Sandbox Code Playgroud)

DataMap 是来自查询的数据。我需要将每个元素与一个数量相匹配,这是通过在每个子元素中保留单独的状态来完成的,但我需要父元素能够访问该数量。

export function DataMap(props){
  
  const onChange = (el, quantity) => {
    console.dir(el);
    props.onChange(el, quantity);
  };

  if (props.target === "") return <div>No target.</div>;
  if (props.target !== "" && validateAddress(props.target) !== 3) return <div>Invalid address.</div>;
  if (props.error) {
    console.log(props.error);
    return <div>Failed to Load</div>;
  }
  if (!props.data) return <div>Loading...</div>;
  if (!props.error && props.data){

    return <Row>
    {props.data["listing"]
    .map((el, i , arr) => {
      return (
      <Col key={i} id={i} xs={4} sm={4} md={3} lg={2}>
          <StateImg src={"https://ipfs.io/ipfs/" + el["token"]["display_uri"].slice(7,)}/>
          <h5>{el["token"]["name"]}</h5>
          <p>{el["price"] / 1000000} {" xtz"}</p>
          <Row>
            <QuantityForm remaining={el["amount_left"]} onChange={onChange} element={el}/>
          </Row>
      </Col>)      
    })}
    </Row>
  }
}
Run Code Online (Sandbox Code Playgroud)

最后,QuantityForms 只是每个项目数量的表单输入。现在状态保存在每个单独的元素中并传递到父级的“批量”状态,但这意味着除了使用这些特定输入之外我无法更改数量。

export function QuantityForm(props){
  const [quantity, setQuantity] = useState(0);

  useEffect(()=>{
    props.onChange(props.element, quantity); 
  }, [props.element, quantity]);

  const onChange = (e) => {
    setQuantity(parseInt(e.target.value));  
    e.preventDefault();   
  };

  return (
    <Form.Group>
      <Form.Label>Quantity</Form.Label>
      <InputGroup>
        <Form.Control onChange={onChange} onKeyDown={(e)=>{e.preventDefault();}} type={"number"} value={quantity} min={0} max={props.remaining} aria-describedby="basic-addon1"/>
          <InputGroup.Text id="basic-addon1">
            {"/" + props.remaining}
          </InputGroup.Text>
      </InputGroup>
    </Form.Group>
  );
}
Run Code Online (Sandbox Code Playgroud)

非常感谢任何使用 Refs 访问映射的 QuantityForms 值的帮助。[1]:如何在地图中使用 React useRef 来定位 DOM

Iva*_*lin 4

你在这里不需要裁判。“React 方式”是将状态移至公共父级。因此,如果您想修改数量QuantityForm和 in 的数量CartModal,那么您应该将其保留在Home组件中。

让我们使用batch它:

const [batch, setBatch] = useState([]); // [{index, count}]
Run Code Online (Sandbox Code Playgroud)

你不需要一个状态batchCount。算算,便宜:

const batchCount = batch.reduce((sum, item) => sum + item.count, 0);
Run Code Online (Sandbox Code Playgroud)

在这里,我们更新现有项目,插入新项目并删除那些count === 0

const onChange = (index, count) => {
  if (count === 0) {
    setBatch(batch.filter((b) => b.index !== index));
  } else {
    const found = batch.find((b) => b.index === index);
    if (found) {
      setBatch(batch.map((b) => (b.index === index ? { index, count } : b)));
    } else {
      setBatch([...batch, { index, count }]);
    }
  }
};
Run Code Online (Sandbox Code Playgroud)

请注意,以下内容在 React 中不起作用,因为Object.is(batch, batchCopy) === true

let batchCopy = batch;
...
setBatch(batchCopy);
Run Code Online (Sandbox Code Playgroud)

让我们渲染一下Home组件:

return (
  <div>
    <TopBar cartTotal={batchCount} />
    <DataMap data={data} batch={batch} onChange={onChange} />
    <CartModal data={data} batch={batch} onChange={onChange} />
  </div>
);
Run Code Online (Sandbox Code Playgroud)

data包含有关产品的所有信息,它是是非反应值。

batch仅包含数量,并且是无功值。

const TopBar = ({ cartTotal }) => {
  return (
    <div>
      <h2>TopBar</h2>
      <h3>Cart total: {cartTotal}</h3>
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)
const DataMap = ({ data, batch, onChange }) => {
  return (
    <div>
      <h2>DataMap</h2>
      {data.map(({ token: { name }, price, amount_left }, index) => (
        <div key={name}>
          <div>name: {name}</div>
          <div>price: {price}</div>
          <QuantityForm
            value={batch.find((b) => b.index === index)?.count || 0}
            maxValue={amount_left}
            onChange={(v) => onChange(index, v)}
          />
        </div>
      ))}
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)
const QuantityForm = ({ value, maxValue, onChange }) => {
  return (
    <div style={{ display: "flex" }}>
      {value} / {maxValue}
      <button onClick={(e) => onChange(Math.min(value + 1, maxValue))}>
        +
      </button>
      <button onClick={(e) => onChange(Math.max(value - 1, 0))}>-</button>
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)
const CartModal = ({ data, batch, onChange }) => {
  return (
    <div>
      <h2>CartModel</h2>
      {batch.map(({ index, count }) => (
        <div key={index}>
          {data[index].token.name}: {count}
          <button onClick={(e) => onChange(index, 0)}>Cancel</button>
        </div>
      ))}
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)

工作示例