嵌套异步函数中的 setState - React Hooks

setState in nested async function - React Hooks

我如何构建一个异步获取一些数据然后使用该数据获取更多异步数据的函数?

我正在使用 Dexie.js(indexedDB 包装器)来存储有关直接消息的数据。我在对象中存储的一件事是我将向其发送消息的用户 ID。为了构建更好的 UI 我还获得了一些关于该用户的信息,例如存储在远程 rdbms 上的个人资料图片、用户名和显示名称。要构建一个完整的 link 组件,需要来自两个数据库(本地 indexedDB 和远程 rdbms)的数据。

我的解决方案return是一个空数组。在 Google Chrome 中记录它时正在计算它,我确实看到了我的数据。但是,因为这不是在渲染时计算的,所以数组总是空的,因此我无法迭代它来构建组件。

const [conversations, setConversations] = useState<IConversation[]>()
const [receivers, setReceivers] = useState<Profile[]>()
useEffect(() => {
    messagesDatabase.conversations.toArray().then(result => {
      setConversations(result)
    })
  }, [])

  useEffect(() => {
    if (conversations) {
      const getReceivers = async () => {
        let receivers: Profile[] = []
        await conversations.forEach(async (element) => {
          const receiver = await getProfileById(element.conversationWith, token)
          // the above await is a javascript fetch call to my backend that returns json about the user values I mentioned
          receivers.push(receiver)
        })
        return receivers
      }
      getReceivers().then(receivers => {
        setReceivers(receivers)
      })
    }
  }, [conversations])
  /*
    The below log logs an array with a length of 0; receivers.length -> 0
    but when clicking the log in Chrome I see:
    [
     0: {
       avatarURL: "https://lh3.googleusercontent.com/..."
       displayName: "Cool guy"
       userId: "1234"
       username: "cool_guy"
     }
     1: ...
    ]

  */
  console.log(receivers) 

我的计划是然后使用 map

遍历此数组
{
  receivers && conversations
  ? receivers.map((element, index) => {
    return  <ChatLink 
              path={conversations[index].path}
              lastMessage={conversations[index].last_message}
              displayName={element.displayName}
              username={element.username}
              avatarURL={element.avatarURL}
              key={index}
            />
    })
  : null
}

我怎样才能写这个而不是 return 一个空数组?
这是一个与我遇到的问题相关的问题

请将接收器初始值设置为数组

const [receivers, setReceivers] = useState<Profile[]>([])

此外,foreach 不会像您期望的那样等待,使用 for 循环而不是 foreach

我不确定它是否能解决您的问题 但它可以帮助您解决错误

我认为这是因为 getReceivers() 函数是异步的。它等待响应,同时你的状态用空数组呈现。

您可以显示微调器,直到收到响应。 喜欢

const[isLoading,setLoading]= useState(true)
    useEffect(()=>{
         getReceivers().then(()=>{setLoading(false)}).catch(..)
        } )
  return  {isLoading ? <spinner/> : <yourdata/>}

我认为您的问题与您尝试执行以下操作时的第二个 useEffect 挂钩有关:

const getReceivers = async () => {
  let receivers: Profile[] = []
  await conversations.forEach(async (element) => {
    const receiver = await getProfileById(element.conversationWith, token)
      receivers.push(receiver)
    })
    return receivers
   }
   getReceivers().then(receivers => {
     setReceivers(receivers)
   })
}

不幸的是,这行不通,因为 async/await 不适用于 forEach。您需要使用 for...ofPromise.all() 来正确遍历所有 conversations,调用您的 API,然后在完成后设置 state

这是一个使用 Promise.all() 的解决方案:

function App() {
  const [conversations, setConversations] = useState<IConversation[]>([]);
  const [receivers, setReceivers] = useState<Profile[]>([]);

  useEffect(() => {
    messagesDatabase.conversations.toArray().then(result => {
      setConversations(result);
    });
  }, []);

  useEffect(() => {
    if (conversations.length === 0) {
      return;
    }
    async function getReceivers() {
      const receivers: Profile[] = await Promise.all(
        conversations.map(conversation =>
          getProfileById(element.conversationWith, token)
        )
      );
      setReceivers(receivers);
    }
    getReceivers()
  }, [conversations]);

  // NOTE: You don't have to do the `receivers && conversations`
  // check, and since both are arrays, you should check whether
  // `receivers.length !== 0` and `conversations.length !== 0`
  // if you want to render something conditionally, but since your
  // initial `receivers` state is an empty array, you could just 
  // render that instead and you won't be seeing anything until 
  // that array is populated with some data after all fetching is
  // done, however, for a better UX, you should probably indicate
  // that things are loading and show something rather than returning
  // an empty array or null
  return receivers.map((receiver, idx) => <ChatLink />)

  // or, alternatively
  return receivers.length !== 0 ? (
    receivers.map((receiver, idx) => <ChatLink />)
  ) : (
    <p>Loading...</p>
  );
}

或者,使用 for...of,您可以执行以下操作:

function App() {
  const [conversations, setConversations] = useState<IConversation[]>([]);
  const [receivers, setReceivers] = useState<Profile[]>([]);

  useEffect(() => {
    messagesDatabase.conversations.toArray().then(result => {
      setConversations(result);
    });
  }, []);

  useEffect(() => {
    if (conversations.length === 0) {
      return;
    }
    async function getReceivers() {
      let receivers: Profile[] = [];
      const profiles = conversations.map(conversation =>
        getProfileById(conversation.conversationWith, token)
      );
      for (const profile of profiles) {
        const receiver = await profile;
        receivers.push(receiver);
      }
      return receivers;
    }
    getReceivers().then(receivers => {
      setReceivers(receivers);
    });
  }, [conversations]);

  return receivers.map((receiver, idx) => <ChatLink />);
}