当我尝试为列表中的每个项目创建一个组件时,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 &lt;ReportComponent data=data /&gt;null 之后缺少&amp;&amp;。此外,您使用&lt;Box&gt; 打开但使用&lt;/Grid&gt; 关闭。请使用 minimal reproducible example 来更新您的问题,以展示问题,最好是使用 Stack Snippets([&lt;&gt;] 工具栏按钮)的 runnable 问题。 Stack Snippets 支持 React,包括 JSX; here's how to do one. 我更新了它 - 我将尝试制作一个 Stack Snippets,但上面的代码几乎就是我所拥有的(除非在页面中调用 ReportComponent 并呈现) 代码对我来说看起来不错(除了您需要在Typography 返回的Typography 元素上使用key),所以我将它复制并粘贴到一个sn-p 中并写了为TypographyBoxuseFetchHooik 设置一些占位符/替身。它工作得很好。请更新 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参数改变值(旧值不是===新值),它会重复获取并更新dataerror

钩子也存在各种问题,主要问题是它执行异步工作(一系列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 滚动。当我点击屏幕上的最后一个项目时它就失败了

从父组件中生成生成列表的本机/ Redux状态

删除一项后组件列表不更新状态

为 jquery ui 可排序列表中的每个项目添加一个删除按钮

当我单击列表中的每个项目时,我需要获取项目的内容(图像和文本)

自定义组件中的RadioButton功能