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



我有一个应用程序,它从GraphQL数据库中提取数据,然后将其映射到自定义表单组件(数量号文本框(中。现在,组件本身保持着各自数量的状态,但我需要能够访问Parent的值,这样我就可以使用应用程序中其他地方的输入来更改数量。我已经研究了如何做到这一点,我认为这可能是我需要的,但我不知道如何应用它:[如何在地图中使用React useRef瞄准DOM][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>
)
}

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

最后,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>
);
}

非常感谢使用Refs访问映射的QuantityForms的值的任何帮助。[1] :目标DOM如何在地图中使用useRef进行反应

这里不需要引用。";反应方式";是将状态上移到公共父级。因此,如果要修改QuantityFormCartModal中的数量,则应将其保留在Home组件中。

让我们使用batch

const [batch, setBatch] = useState([]); // [{index, count}]

batchCount不需要状态。计算一下,它很便宜:

const batchCount = batch.reduce((sum, item) => sum + item.count, 0);

在这里,我们更新现有项目,插入新项目,并使用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 }]);
}
}
};

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

let batchCopy = batch;
...
setBatch(batchCopy);

让我们渲染Home组件:

return (
<div>
<TopBar cartTotal={batchCount} />
<DataMap data={data} batch={batch} onChange={onChange} />
<CartModal data={data} batch={batch} onChange={onChange} />
</div>
);

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

batch只包含量,它是反应性值。

const TopBar = ({ cartTotal }) => {
return (
<div>
<h2>TopBar</h2>
<h3>Cart total: {cartTotal}</h3>
</div>
);
};
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>
);
};
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>
);
};
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>
);
};

工作示例

最新更新