当我尝试为列表中的每个项目创建一个组件时,Reactjs 覆盖组件
Reactjs overwriting component when I try to create a component per item in my list
我有一组类似于以下内容的数据:
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) 它 returns 我上面显示的数据时) , 任何一个
答: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"
width="100%"
>
<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>
);
}
---- 使用建议的获取再次更新,不幸的是仍然覆盖 ---
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>
您的 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
版本,请参阅评论:
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 };
}
这是一个 草图,如果您需要对其进行一些小的调整,我不会感到惊讶,但它应该会让您走上正确的道路。
我有一组类似于以下内容的数据:
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) 它 returns 我上面显示的数据时) , 任何一个 答: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"
width="100%"
>
<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>
);
}
---- 使用建议的获取再次更新,不幸的是仍然覆盖 ---
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>
您的 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
版本,请参阅评论:
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 };
}
这是一个 草图,如果您需要对其进行一些小的调整,我不会感到惊讶,但它应该会让您走上正确的道路。