将检索到的对象推送到 React 中的状态只会给我带来最后一个

Posted

技术标签:

【中文标题】将检索到的对象推送到 React 中的状态只会给我带来最后一个【英文标题】:Push retrieved objects to state in React only brings me the last one 【发布时间】:2022-01-21 11:04:13 【问题描述】:

我正在从 json 中获取数据。我想在我的 React 组件中显示该数据。但是每次我尝试将从 json 返回的对象传递给我的状态时,它只返回一个并删除前一个,而不是 json 中的整个元素。

这是我的代码。

const [state, setState] = useState();

  const connection = new Connection("devnet");
  
  const  publicKey  = useWallet();
  
  useEffect(() => 
    (async () => 
      //if not public key, close
        if(!publicKey) return;
        //get tokens
        let response = await connection.getTokenAccountsByOwner(
          publicKey!, // owner here
          
            programId: TOKEN_PROGRAM_ID,
          
        );
        response.value.forEach((e) => 
          const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
          //get only tokens with value of 1
          if ( parseInt(`$SPLToken.u64.fromBuffer(accountInfo.amount)`) === 1 ) 
            const tokenPublicKey = `$new PublicKey(accountInfo.mint)`
            //get the metadata of each NFT
            const run = async () => 

              const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
              //get only tokens of the collection ...
              if (ownedMetadata.data.updateAuthority === "Address_authority") 
              //show the json data from arweave
              let url= ownedMetadata.data.data.uri;
              fetch(url)
              .then(res => res.json())
              .then((out) => 

                setState(prevState => 
                  // THIS IS NOT WORKING FOR ME :(
                  return ...prevState, ...out;
                );
              )
              .catch(err =>  throw err );
             
          ;
  
          run();
          
        );
        
    )()
    , [connection, publicKey]);

  console.log(state)

【问题讨论】:

可能不相关,但这可能是一堆不同的状态变化(尽管response.value 中有许多元素)。你可能想要完成所有的获取,然后更新一次状态。 还要注意.catch(err => throw err ) 没有做任何有用的事情。 【参考方案1】:

...prevState, ...out; 创建一个新对象,将prevState 自己的所有属性放在新对象上,然后将out 自己的所有属性放在新对象上(覆盖prevState 中的值如果prevState 也有具有这些名称的属性。

听起来你想要一个数组,而不是单个对象:

const [state, setState] = useState([]);

然后设置:

setState(prevState => [...prevState, out]);

可能不相关,但这可能是一堆不同的状态变化(response.value 中的每个元素一个)。因为工作是异步的,这也可能导致一些临时重新渲染。也许你想要那个,但如果你不这样做,你可以完成所有的获取,然后更新一次状态。此外,任何时候您在 useEffect 中进行异步工作时,您都应该考虑到效果的依赖关系同时发生变化或组件已卸载的可能性。像这样的东西(见***cmets):

const [state, setState] = useState();

const connection = new Connection("devnet");

const  publicKey  = useWallet();

useEffect(() => 
    // *** Use a controller to stop when the component unmounts, etc.
    const controller = new AbortContoller();
    const signal = controller;
    (async () => 
        if (signal.aborted) return; // ***

        // If not public key, stop
        if (!publicKey) return;

        // Get tokens
        let response = await connection.getTokenAccountsByOwner(
            publicKey!, // owner here
            
                programId: TOKEN_PROGRAM_ID,
            
        );

        // *** Build up the new data in this array (since we skip some elements,
        // so we won't have a simple 1:1 mapping of `response.value` elements
        // to result elements.
        const newState = [];
        // *** Wait for all the operations to finish and add their elements to `newState`
        await Promise.all(
            response.value.map(async (e) => 
                const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
                // Skip tokens with value other than 1
                if (parseInt(`$SPLToken.u64.fromBuffer(accountInfo.amount)`) !== 1) 
                    return;
                
                const tokenPublicKey = `$new PublicKey(accountInfo.mint)`;
                const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
                // Get only tokens of the collection ...
                if (ownedMetadata.data.updateAuthority !== "Address_authority") 
                    return;
                
                // Show the data from arweave
                let url = ownedMetadata.data.data.uri;
                const response = await fetch(url, signal); // *** Pass the signal to `fetch`
                if (!response.ok)  // *** This check was missing
                    throw new Error(`HTTP error $response.status`); // Or ignore if you prefer
                
                const out = await response.json();
                newState.push(out);
            )
        );
        // *** Now we have all of them, do one state update
        setState(prevState = [...prevState, ...newState]);
    )();

    return () => 
        // Stop if our dependencies change or the component unmounts
        controller.abort();
    ;
, [connection, publicKey]);

【讨论】:

即使这样,在异步调用之后多次在 forEach 中设置状态也没问题。映射所有承诺并获得最终结果不是更好吗? @TusharShahi - 这完全取决于用例,但I raised that question as well。 :-) 好的。我的印象是这样的事情甚至都行不通,并且总是先映射然后设置状态。也许它确实有效 @TusharShahi - 它之所以有效,是因为它使用了状态设置器的回调版本。但是它会排队很多状态更新,并且根据异步时间可能会在结果出现时重新渲染。有时这就是你想要的,但我经常会说它不是。 :-) @TusharShahi - 我决定补充一点。

以上是关于将检索到的对象推送到 React 中的状态只会给我带来最后一个的主要内容,如果未能解决你的问题,请参考以下文章

显示从 http 调用 Angular 检索到的更新数据

在 react 中使用 setState 将预定义的对象推送到数组中

使用Angular2中的接口将对象推送到新数组中

将所有数据推送到数组后设置状态

将元素推送到数组时反应setstate不起作用

循环遍历两个对象数组以将匹配值推送到新数组在 React 中不起作用,但在 JS Fiddle 中起作用