为何设置key后React组件状态未重置?filter_obj变更异常
问题:filter_obj变化时组件状态无法彻底重置
我希望在filter_obj发生变化时重置组件之前的状态,虽然已经在useEffect里重置了page和itemData,但它们会立刻恢复成原来的值。
父组件里给Archive组件设置了key:
<Archive key = {filter_obj.length} state={state_filter}/>
理论上filter_obj长度变化时,旧组件会被销毁、新组件重新挂载,所有状态应该被清空,但itemData还是会恢复成之前的值。
以下是使用react-infinite-scroll-component的组件代码:
export default function Archive(props) { const [width, height] = useWindowSize() const [manifest, setManifest]= useState({}); const [open, setOpen] = useState(false); const [apiUrl,setapiurl] = useState([]) const [itemData, setData] = useState([]) const [hasMore, setHasMore] = useState(true) const {filter_obj, set_filter_obj} = props.state; const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(16); const [total, setTotal] = useState(0) const handleOpen = (manifest) => { setOpen(true); setManifest(manifest)} const handleClose = () => setOpen(false); const modalStyle = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", p: 4, maxHeight: 500, }; const fetchData = async () => { var data = new FormData(); data.append("hierarchical", "False"); data.append("selected_fields", 'url'); data.append("results_per_page", rowsPerPage); data.append("results_page", page + 1); for (const property in filter_obj) { filter_obj[property].forEach((v) => { data.append(property, v); })} const res = await fetch('url',{ method: 'POST', body: data, headers: {'Authorization':AUTH_TOKEN} }) const result = await res.json(); setapiurl(result) const fetchImage = async ()=>{ const promises = result.map(uri => { return fetch(Object.values(uri), { method: "GET", }).then(res => { return res.json() }).then(res => { var dict = {"title": res.label.none[0], "image": curImage(res.thumbnail[0].id),"uri":uri} return dict; }) }) const response = await Promise.all(promises) setData([...itemData, ...response]) if(response.length<=0){ setHasMore(false) } } fetchImage().catch(console.error); } useEffect(()=>{ setPage(0); setData([]) setapiurl( []) fetchData() }, [filter_obj]) return ( <div id="infinite-container" style={{ overflow: 'auto'}}> <InfiniteScroll dataLength={itemData.length} //This is important field to render the next data next={fetchData} getScrollParent={()=>document.getElementById('infinite-container')} useWindow={false} hasMore={hasMore} loader={<h4>Loading...</h4>} endMessage={ <p style={{ textAlign: 'center' }}> end message </p> } > <ImageList sx={{ width: width, height: "99%" }} cols={ Math.sqrt(rowsPerPage) } gap={30} > {itemData.map((item) => ( <ImageListItem key={item.image}> <img src={`${item.image}?w=248&fit=crop&auto=format`} srcSet={`${item.image}?w=248&fit=crop&auto=format&dpr=2 2x`} alt={item.title} loading="lazy" /> <ImageListItemBar title={item.title} sx ={{ bgcolor: alpha('#549165',0.8) }} actionIcon={ <IconButton sx={{ color: 'rgba(255, 255, 255, 0.54)' }} aria-label={`info about ${item.title}`} onClick={() => handleOpen(item.uri.url)} > <InfoIcon /> </IconButton> } /> </ImageListItem> ))} </ImageList> </InfiniteScroll> </div> ); }
解决方案
问题核心
- key取值逻辑缺陷:用
filter_obj.length作为key时,如果两次筛选操作后长度相同(比如先选2个条件,取消1个后再选1个回到长度2),key值重复,React会判定为同一个组件,不会触发重新挂载,状态自然无法重置。 - 闭包陷阱导致状态复用:
fetchData函数依赖了page和itemData的旧值,useEffect里调用fetchData时,状态更新是异步的,函数执行时拿到的还是未重置前的旧状态,导致新请求的数据和旧数据拼接在一起。
修复步骤
1. 更换组件key的取值
改用filter_obj的内容生成唯一key,确保只要筛选条件变化,组件就会重新挂载:
<Archive key={JSON.stringify(filter_obj)} state={state_filter}/>
2. 修复fetchData的闭包问题
采用函数式更新获取最新状态,同时给fetchData传入明确的页码参数,避免依赖旧状态:
const fetchData = async (currentPage) => { var data = new FormData(); data.append("hierarchical", "False"); data.append("selected_fields", 'url'); data.append("results_per_page", rowsPerPage); // 使用传入的最新页码,避免闭包拿旧值 data.append("results_page", currentPage + 1); for (const property in filter_obj) { filter_obj[property].forEach((v) => { data.append(property, v); }); } const res = await fetch('url',{ method: 'POST', body: data, headers: {'Authorization':AUTH_TOKEN} }) const result = await res.json(); setapiurl(result); const fetchImage = async ()=>{ const promises = result.map(uri => { return fetch(Object.values(uri), { method: "GET", }).then(res => res.json()) .then(res => { return {"title": res.label.none[0], "image": curImage(res.thumbnail[0].id),"uri":uri}; }) }) const response = await Promise.all(promises); // 函数式更新,确保拿到最新的itemData setData(prev => [...prev, ...response]); setHasMore(response.length > 0); } fetchImage().catch(console.error); }
3. 完整重置所有相关状态
在useEffect里重置状态时,别忘了重置hasMore,避免新筛选条件下无法触发请求:
useEffect(()=>{ setPage(0); setData([]); setapiurl([]); setHasMore(true); // 传入明确的初始页码0 fetchData(0); }, [filter_obj])
内容的提问来源于stack exchange,提问作者Alison Qiu
相关产品推荐
相关产品推荐

