当我尝试为列表中的每个项目创建一个组件时,Reactjs 覆盖组件
Posted
技术标签:
【中文标题】当我尝试为列表中的每个项目创建一个组件时,Reactjs 覆盖组件【英文标题】:Reactjs overwriting component when I try to create a component per item in my list 【发布时间】:2022-01-08 06:56:07 【问题描述】:我有一个类似于以下的数据数组:
data = [name: 'A', data: 1, name: 'B', data: 2]
我也有类似以下的代码:
function ReportComponent( data )
return data.map((datum) => (
<Typography>
datum.name: datum.data
</Typography>
));
被调用
function ReportBox( component )
const data = useFetchHook(component.urls)
// data returns exactly as expected, an array of objects
return (
<Box>
<Typography>
component.title
</Typography>
data !== null && <ReportComponent data=data />
</Box>
);
我的问题是,当我运行应用程序时,我只能从我的数据中得到一个输出(当我 console.log(data) 它返回我上面显示的数据时),要么 答:1 或 B:2。我希望组件中都存在两者。有什么建议吗?
---- 更新---- 使用Fetch函数
import useState, useEffect from 'react';
function useFetch(urls)
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() =>
let i = urls.length - 1;
const result = [];
while (i >= 0)
const abortCont = new AbortController();
console.log(`url $i`);
console.log(urls[i]);
fetch(urls[i], signal: abortCont.signal , mode: 'cors' )
.then((res) =>
if (!res.ok)
console.log('something went wrong with the data fetch');
return res.json(); // why?
)
.then((data) =>
result.push(data);
setData(result);
)
.catch((err) =>
if (err.name === 'AbortError')
console.log('aborted');
else
setError(err.message);
);
i -= 1;
, [urls]);
// console.log(data);
return data, error ;
export default useFetch;
--- 更新 DashBox ---
mport Box, Grid, Container, Typography from '@mui/material';
import ReportBox from './ReportBox';
function DashBox( components )
// console.log(components);
return (
<Grid
item
columns=5
sx=
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'stretch',
marginTop: '20px',
marginLeft: '5px'
>
components.map((component) => (
<ReportBox component=component />
))
</Grid>
);
export default DashBox;
--- 更新页面---
export default function Page()
const optionsFilter= [
'A',
'B',
'C'
];
const [filter, setFilter] = useState('A');
const componentsPage = [
title: 'One',
urls: [
`http://localhost:9000/page1?filter=$filter`,
`http://localhost:9000/page2?filter=$filter`
]
];
const componentsPageGraphs = [
title: 'OneGraph',
urls: [
`http://localhost:9000/page1?filter=$filter`,
`http://localhost:9000/page2?filter=$filter`
]
];
return (
<Page title="Page">
<Container>
<Typography variant="h4" sx= mb: 5 >
Page
</Typography>
<Container marginBottom="10px">
<Typography marginLeft="5px" variant="h5">
Filters
</Typography>
<Grid
columns=5
sx=
display: 'flex',
flexDirection: 'row',
alignItems: 'stretch',
marginTop: '10px',
marginLeft: '5px',
justifyContent: 'space-evenly'
>
<Grid item sx= pr: 5 >
<DropDown
options=optionsFilter
title="Filter Type"
setData=setFilter
data=filter
key="one"
/>
</Grid>
</Grid>
</Container>
<br />
<Box
container
sx= border: 2
marginLeft="20px"
pr="20px"
pb="20px"
pl="20px"
>
<Typography variant="h3">Page Dashboard</Typography>
<DashBox components=componentsPage />
</Box>
<Grid container spacing=2 marginTop="20px">
componentsPageGraphs.map((component) => (
<Grid item xs=6>
<Typography>component.title</Typography>
<LineChart xtype="category" urls=component.urls />
</Grid>
))
</Grid>
</Container>
</Page>
);
---- 使用建议的 fetch 再次更新,不幸的是仍然覆盖 ---
import useState, useEffect, useRef from 'react';
const sameContents = (array1, array2) =>
array1.length === array2.length && array1.every((value, index) => value === array2[index]);
function useFetch(urls)
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const urlsRef = useRef(null);
if (!urlsRef.current || !sameContents(urlsRef.current, urls))
urlsRef.current = urls.slice();
useEffect(() =>
const results = [];
if (!urlsRef.current)
return;
const controller = new AbortController();
const signal = controller;
Promise.all(
urlsRef.current.map((url) =>
fetch(url, signal, mode: 'cors' )
.then((res) =>
if (!res.ok)
console.log('http issue');
return res.json();
)
.then((data) =>
if (!signal.aborted)
results.push(data);
setData(results);
setError(null);
)
.catch((error) =>
if (signal.aborted)
return;
setData(null);
setError(error);
);
return () =>
controller.abort();
;
)
);
, [urlsRef.current]);
return data, error ;
export default useFetch;
堆栈片段:
const useState, useEffect = React;
// Fake Typography component
const Typography = (children) => <div>children</div>;
// Fake Box component
const Box = (children) => <div>children</div>;
// Fake fetch hook
function useFetchHook(urls)
const [data, setData] = useState(null);
useEffect(() =>
setTimeout(() =>
setData([
name: "One", data: "Data for 'One'",
name: "Two", data: "Data for 'Two'",
name: "Three", data: "Data for 'Three'",
]);
, 500);
, []);
return data;
function ReportComponent( data )
return data.map((datum) => (
<Typography>
datum.name: datum.data
</Typography>
));
function ReportBox( component )
const data = useFetchHook(component.urls)
// data returns exactly as expected, an array of objects
return (
<Box>
<Typography>
component.title
</Typography>
data !== null && <ReportComponent data=data />
</Box>
);
ReactDOM.render(<ReportBox component=urls: [], title: "Example" />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
【问题讨论】:
我预计您会遇到语法错误,data !== null <ReportComponent data=data />
在null
之后缺少&&
。此外,您使用<Box>
打开但使用</Grid>
关闭。请使用 minimal reproducible example 来更新您的问题,以展示问题,最好是使用 Stack Snippets([<>]
工具栏按钮)的 runnable 问题。 Stack Snippets 支持 React,包括 JSX; here's how to do one.
我更新了它 - 我将尝试制作一个 Stack Snippets,但上面的代码几乎就是我所拥有的(除非在页面中调用 ReportComponent 并呈现)
代码对我来说看起来不错(除了您需要在Typography
返回的Typography
元素上使用key
),所以我将它复制并粘贴到一个sn-p 中并写了为Typography
、Box
和useFetchHooik
设置一些占位符/替身。它工作得很好。请更新 sn-p 以演示问题(并更新 sn-p 之外的问题中的代码以匹配,或者只是将其删除并取消选中“默认隐藏 sn-p”框)。
所以它会短暂显示一切正常,然后它会切换回只有一个项目。我知道数据在我将其记录到控制台时按预期存在,但它会在交换为组件中只有一项后立即出现。我认为问题出在我获取数据时,我更新了我将使用 useState 返回的值。但是,在我的 while 循环完成之前,我不会返回 data 。我不确定为什么会发生此错误。任何建议将不胜感激!
现在添加断点。顺便说一句,非常感谢!
【参考方案1】:
您的Page
组件每次在组件中创建一个new componentsPage
对象,其中包含new urls
数组。那些新的urls
数组最终将传递给useFetch
(又名useFetchHook
),您拥有这个结构:
function useFetch(urls)
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() =>
// ...code that fetches and sets `data`/`error`...
, [urls]);
// console.log(data);
return data, error ;
这意味着每次urls
参数改变值(旧值不是===
新值),它会重复获取并更新data
或error
。
钩子也存在各种问题,主要问题是它执行异步工作(一系列fetch
调用)但不检查以确保它得到的结果没有过时(因为urls
已更改)。稍后会详细介绍。
由于每次都重新创建 urls
数组,useFetch
每次都会再次进行提取,因为没有任何数组是 ===
任何其他数组,即使它们具有相同的内容:
console.log(["1", "2", "3"] === ["1", "2", "3"]); // false
所以你需要:
让useFetch
仅在 URL 真正更改时才开始一系列新的提取。如果它被赋予了一个具有相同内容的新数组,它不应该执行一组新的提取。
如果useFetch
即将获得一组新的urls
,则应该中止正在进行的提取,如果发生这种情况,则不应使用以前的结果。
您似乎已经使用AbortController
开始了#2,但没有人调用它的abort
方法,所以它什么也没做。
这是处理这两个事情的useFetch
版本,请参阅 cmets:
const sameContents = (array1, array2) =>
return array1.length === array2.length &&
array1.every((value, index) => value === array2[index]);
;
function useFetch(urls)
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const urlsRef = useRef(null); // A place to keep the URLs we're handling
if (!urlsRef.current || // Mounting, or
!sameContents(urlsRef.current, urls) // Called after mount with *different* URLs
)
// Remember these URLs
urlsRef.current = urls.slice();
useEffect(() =>
if (!urlsRef.current)
// Nothing to do
return;
// Use the same controller and signal for all the fetches
const controller = new AbortController();
const signal = controller;
// Use `Promise.all` to wait for all the fetches to complete (or one
// of them to fail) before setting `data`.
Promise.all(urlsRef.current.map(url =>
// Note: You had ` mode: "cors" ` on its own as a third argument,
// but it should have been part of the second argument (`fetch`
// only takes two).
fetch(url, signal, mode: "cors")
.then(res =>
if (!res.ok)
// HTTP error
throw new Error(`HTTP error $res.status`);
// HTTP okay, read the body of the response and parse it
return res.json();
)
))
.then(data =>
// Got all the data. If this set of results isn't out of date,
// set it and clear any previous error
if (!signal.aborted)
setData(data);
setError(null);
)
.catch(error =>
// Do nothing if these results are out of date
if (signal.aborted)
return;
// Clear data, set error
setData(null);
setError(error);
);
// Return a cleanup callback to abort the set of fetches when we get
// new URLs.
return () =>
controller.abort();
;
, [urlsRef.current]); // <=== Use this instead of `urls`
return data, error ;
这是一个草图,如果您需要对其进行小幅调整,我不会感到惊讶,但它应该能让您走上正确的道路。
【讨论】:
我通常有一个坚定的“不要发布和运行”政策,但现在是晚餐时间,我被告知它在桌子上,所以...:-) 我非常欣赏这一点。不用担心晚餐,这会给我一些时间看看。祝您晚餐愉快,再次感谢您! 问题 - urlsRef.current 是什么?我不熟悉这个概念。希望晚餐很可爱! @ChristinaStebbins - 很高兴为您提供帮助。ursRef.current
是 ref 的当前值。更多关于钩子hre.
嗨,T.J.!我实施了您的建议 - 它似乎没有解决我的两个数据粒子未显示的问题。我正在上传我的实现。希望可以通过更多的工作来解决问题! (您的实现有效,我似乎仍然在页面的单个渲染中获取数据两次)。也许问题是我将数据附加到结果 const 以使用所有包含在一个位置的数据。如果您有任何想法,请告诉我,但感谢您的帮助!以上是关于当我尝试为列表中的每个项目创建一个组件时,Reactjs 覆盖组件的主要内容,如果未能解决你的问题,请参考以下文章
uiautomator - 当我验证每个列表项中的文本时,无法让 ListView 滚动。当我点击屏幕上的最后一个项目时它就失败了
为 jquery ui 可排序列表中的每个项目添加一个删除按钮