如何等待流星订阅数据完全完成?

时间:2020-11-01 16:34:54

标签: javascript reactjs meteor

我一直在浏览许多问题/答案以及流星文档,但在流星反应方面仍然遇到麻烦,一直等到订阅数据完全加载完毕。我正在尝试做两件事,一件事是,如果从数据库中收到的数组小于在客户端创建的数组,然后再做另一件事,如果它大于或等于此值。

问题出在我的订阅上,数据一次传输一次,因此第一个语句总是至少触发一次。

我想知道在调用函数之前是否有一种方法可以确保数据库中的所有数据均已通过。

 if (import.length < arrayLength) {
      ...
 }
 if (import.length > arrayLength || import.length === arrayLength) {
      ...
 }

下面的完整代码示例

import { DropzoneDialog } from "material-ui-dropzone";
import React, { useState, useEffect, useRef } from "react";
import Button from "@material-ui/core/Button";
import { Cards } from "../../../../both/collections";
import { withTracker } from "meteor/react-meteor-data";
import { makeStyles } from "@material-ui/core/styles";
import { Meteor } from 'meteor/meteor';
import _, { map, object } from 'underscore';

const useStyles = makeStyles({
  importButton: {
    color: "#C8C8C8",
    borderColor: "#C8C8C8",
    "&:hover": {
      backgroundColor: "rgba(72,72,72,0.7)",
      borderColor: "rgba(255, 255, 255)",
      color: "rgba(255, 255, 255)",
    },
  },
});

function importDeck(files, setDeckIsReady, setDeckLength, setCountObj, sub, setSubscription) {

  let reader = new FileReader();
  reader.readAsText(files[0]);

  reader.onload = function () {
    let cardArray = reader.result.split("\n");
    let cardArrayFiltered = cardArray.filter(Boolean)
    const countObj = cardArrayFiltered.reduce((acc, next) => {
      count = next.substr(0, next.indexOf(' ')); 
      name = next.substr(next.indexOf(' ') + 1); 
      return {...acc, [name]: count}
    }, {});
    sub.stop()
    const subscription = Meteor.subscribe('cardSearchTwo', Object.keys(countObj), {onReady() {setDeckIsReady(true)}})
    setDeckLength(Object.keys(countObj).length)
    setCountObj(countObj) 
    setSubscription(subscription)
  };
}

export function DeckImport({importCards, setCurrentDeck, importDeckFinal, search, setSearch}) {
  const classes = useStyles()
  const [open, setOpen] = useState(false);
  const [deckIsReady, setDeckIsReady] = useState(false);
  const [deckLength, setDeckLength] = useState(0);
  const [countObj, setCountObj] = useState({});
  const [subscription, setSubscription] = useState({stop(){}})
  const [isReady, setIsReady] = useState(false)
  console.log(deckIsReady, importCards)
  let missingCards = []
  let fixedDeck = importCards
  useEffect(() => {
    if (importCards.length < deckLength && deckIsReady) {
      fixedDeck = []
      Object.keys(countObj).forEach(card => {
        if (!importCards.some(e => e.name === card)) missingCards.push(card)
      }) 
      fixedDeck = importCards.filter(card => !missingCards.includes(card.name))
      console.log('LESS THAN', fixedDeck.length,  missingCards.length)
      importDeckFinal(fixedDeck, setCurrentDeck, countObj) 
      setDeckIsReady(false) 
      setDeckLength(0) 
      setCountObj({})
    }
    if ((importCards.length > deckLength || importCards.length === deckLength)  && deckIsReady) {
      fixedDeck = []
      importCards.forEach(card => {
        if (card.name in countObj) fixedDeck.push(card)
      })
      console.log('EQUAL', fixedDeck);
      importDeckFinal(fixedDeck, setCurrentDeck, countObj) 
      setDeckIsReady(false) 
      setDeckLength(0) 
      setCountObj({})
    }
  })
  return (
    <div>
      <Button
        variant="outlined"
        color="primary"
        component="label"
        onClick={() => setOpen(true)}
        className={classes.importButton}>
        Import
        </Button>
        <DropzoneDialog
        acceptedFiles={["text/*"]}
        cancelButtonText={"cancel"}
        submitButtonText={"submit"}
        maxFileSize={5000000}
        open={open}
        onClose={() => setOpen(false)}
        onSave={(files) => {
            
            importDeck(
            files,
            setDeckIsReady,
            setDeckLength,
            setCountObj,
            subscription,
            setSubscription,
            setIsReady
            );
          setOpen(false);
          }}
        showPreviews={true}
        showFileNamesInPreview={true}
        />
    </div>
  )
}

export default withTracker(props => {
  const cards = Cards.find({}, { sort: {name: 1}}).fetch();
  const uniqueNames = _.uniq(cards.map(function(x) {return x.name;}), true)
  return {
    importCards: uniqueNames.map(name => cards.find(({ name: cName }) => cName === name))
  };
})(DeckImport);

特别是,只有一部分数据准备就绪时,deckIsReady设置为True。

2 个答案:

答案 0 :(得分:0)

我更熟悉普通的流星订阅/发布逻辑,而不完全确定您的反应逻辑如何影响事物。

上面写着const subscription = Meteor.subscribe('cardSearchTwo', Object.keys(countObj), {onReady() {setDeckIsReady(true)}}),这行代码会重新运行多次,在回调中,您只是设置了甲板已准备就绪的期望值,但是在回调之外,您正在设置甲板长度和其他内容与您刚刚完成的订阅有关-这意味着这些行将在该订阅完成从发布功能获取数据之前发生。

因此,这里的解决方案是将与卡片组相关的那些内容放入回调中,以便可以对其进行正确设置,然后最后通过调用DeckImport来触发响应式setDeckIsReady(true)

答案 1 :(得分:0)

您使用了两件事

  • useTracker React钩子,它是meteor-react-data的一部分。每当反应式数据源发生更改时,此挂钩都会重新运行。

  • 使用Subscription.ready()确定是否可以使用数据源。

将其放在一起,例如:

const importReady = useTracker(() => Meteor.subscribe('cardSearchTwo').ready())