我想通过调用映射到数组(从数据库调用)的函数来更新 useState
数组值,useState
数组将针对(数据库数组)中的每个项目更新,因此我尝试了以下方法:
const [snapshots, setSnapshots] = useState();
const [items, setItems] = useState([]);
// *** get from the database ***** //
useEffect(()=> {
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket")
.get()
.then((snapshot) => {
setSnapshots(snapshot.docs)
}
) ;
}, []);
// *** get from the database ***** //
// *** update items value ***** //
return <div className="cart__items__item">
{snapshots && snapshots.map((doc)=>(
setItems([...items, doc.data().id]),
console.log(items)
))
}
</div>
// *** update items value ***** //
但出现以下错误:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
我尝试查看 console.log
结果以查看问题,并且 Items
数组连续记录在控制台中我尝试将代码包含在 useEffect
中,但它效果不佳。
答案 0 :(得分:4)
永远不要在组件函数的顶层调用状态设置器。对于函数组件,要记住的关键是当您更改状态时,您的函数将使用更新的状态再次被调用。如果您的代码在函数的顶层有状态更改(正如您在问题中所做的那样),则每次函数运行时,您都会更改状态,从而导致函数运行,导致另一个状态更改,依此类推在。在您的代码中:
const initialArray = []; // *** 1
const [Items, setItems] = useState(initialArray) // *** 2
initialArray.push("pushed item")
setItems(initialArray) // *** 3
Items
的初始值相反,您应该仅在响应某些更改或事件(例如单击处理程序或其他一些状态更改等)时设置状态。
另请注意,您不得直接修改处于状态的对象(包括数组)。您的代码在技术上并没有这样做(因为每次都有一个新的 initialArray
),但它看起来像您想要做的。要将状态添加到数组中,您复制该数组并在末尾添加新条目。
以上示例:
function Example() {
const [items, setItems] = useState([]);
const clickHandler = e => {
setItems([...items, e.currentTarget.value]);
};
return <div>
<div>
{items.map(item => <div key={item}>{item}</div>)}
</div>
<input type="button" value="A" onClick={clickHandler} />
<input type="button" value="B" onClick={clickHandler} />
<input type="button" value="C" onClick={clickHandler} />
</div>;
}
(为了保持代码示例简单,UI 有点奇怪。)
请注意,通常将 Items
称为 items
。
重新更新:
setItems
,所以它有上面的问题。相反,您可以在查询数据库的 useEffect
中完成这项工作。setItems
操作期间重复调用 map
。map
中呈现一些内容例如,像这样:
const [snapshots, setSnapshots] = useState();
const [items, setItems] = useState(); // *** If you're going to use `undefined`
// as the initial state of `snapshots`,
// you probably want to do the same with
// `items`
useEffect(()=> {
let cancelled = false;
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket")
.get()
.then((snapshot) => {
// *** Don't try to set state if we've been unmounted in the meantime
if (!cancelled) {
setSnapshots(snapshot.docs);
// *** Create `items` **once** when you get the snapshots
setItems(snapshot.docs.map(doc => doc.data().id));
}
})
// *** You need to catch and handle rejections
.catch(error => {
// ...handle/report error...
});
return () => {
// *** The component has been unmounted. If you can proactively cancel
// the outstanding DB operation here, that would be best practice.
// This sets a flag so that it definitely doesn't try to update an
// unmounted component, either because A) You can't cancel the DB
// operation, and/or B) You can, but the cancellation occurred *just*
// at the wrong time to prevent the promise fulfillment callback from
// being queued. (E.g., you need it even if you can cancel.)
cancelled = true;
};
}, []);
// *** Use `items` here
return <div className="cart__items__item">
{items && items.map(id => <div>{id}</div>)/* *** Or whatever renders ID */}
</div>;
请注意,该代码假定 doc.data().id
是同步操作。
答案 1 :(得分:1)
您在这里看到的是标准的 React 生命周期行为。您的组件将被挂载然后渲染(在您的组件内运行所有代码)。第一次渲染后,它“侦听”您在组件中处理的值的更改,并在检测到任何更改时重新渲染。
您的情况:
const initialArray = [];
const [Items, setItems] = useState(initialArray)
initialArray.push("pushed item")
setItems(initialArray)
我在这里看到了两件坏事:
setItems(initialArray)
时推送一个新项目并更新每个渲染的状态让我们专注于第二个,因为那是导致您出现问题的那个。如果您想避免无休止的渲染周期,那么您应该将 setItems()
调用移动到不在每次渲染上运行的方法。在功能组件之前,这将在 componentDidMount()
函数中完成。在功能组件中,这是使用 useEffect 钩子完成的:
useEffect(() => {
// Your code here
}, [])
注意提供给 useEffect 的空数组。此数组列出了哪些依赖项应导致此 useEffect 运行。如果你把它留空,它只会运行一次,就像旧的 componentDidMount() 函数一样。
因此,为了解决无休止的渲染问题,您需要将 setItems()
移动到 useEffect 钩子中。