繁体   English   中英

在 useEffect 内部的侦听器中更新 state

[英]Update state within listener that is inside useEffect

我有一个名为useQueryEvents的挂钩,它 1) 为用户获取所有过去的交易,以及 2) 监听 .network 的传入/传出交易。 在这两种情况下,交易都被传递到 function addActionToActivity中,它只是将其附加到活动数组并在键activity下的上下文 state 中更新它。

我无法让活动正确同步。 每当 state 更新时,它都没有最后一笔交易,因为它总是落后一步。 如果我向依赖项添加activity ,它会起作用,但随后会启动一个新的侦听器(由于使用新activity值再次调用整个 function),这会导致类似无限的循环不断切换 state。

function useQueryEvents() {
  const { state: { connectedNetwork, selectedWallet, activity },
  } = useContext(LocalContext);

  useEffect(() => {
    async function bootstrapQueryEvents() {
      // First get all the past transactions
      const transactions = await getAllPastTransactions();
      const contract = await getContract();

      // Now save them to context state via addActionToActivity
      await addActionToActivity(transactions, activity);

      // Now that all the past transactions have been saved
      // listen to all incoming/outgoing transactions and
      // save to context state via addActionToActivity
      contract.on('Transfer', async (from, to, amount, event) => {
        console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
        const transaction = await formatEventToTransaction(event);
        await addActionToActivity(transaction, activity);
      });
    }

    bootstrapQueryEvents();
  }, [selectedAsset, connectedNetwork, selectedWallet]); // <- I've tried adding `activity` here
}

有什么想法可以在启动侦听器的新实例的情况下访问侦听器内部更新的activity值的同时更新 state 吗? 或者也许我可以完全采用不同的方法?

提前致谢

您可以通过将您的逻辑分成两个 useEffects 来解决这个问题。 现在你做两件事:

  1. 你获取交易
  2. 设置事件监听器

问题是如果不同时做这两件事,你就不能再次运行这个钩子。 正如您所说,活动 object 不是您想要的,因为活动 object 是在设置事件侦听器时传递的内容。

将它分成两个钩子可能看起来像这样:

function useQueryEvents() {
  const { state: { connectedNetwork, selectedWallet, activity },
  } = useContext(LocalContext);
  const [contract, setContract] = React.useState()

  // Fetch transactions and setup contract
  useEffect(() => {
    async function fetchTransactionsAndContract() {
      const transactions = await getAllPastTransactions();
      const contract = await getContract();
      
      await addActionToActivity(transactions, activity);

      setContract(contract)
    }
  }, [])

  // Once the contract is set in state, attach the event listener. 
  useEffect(() => {
    if (contract) {
      const handleTransfer = async (from, to, amount, event) => {
        console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
        const transaction = await formatEventToTransaction(event);
        await addActionToActivity(transaction, activity);
      }

      contract.on('Transfer', handleTransfer);

      // Remove event listener, I imagine it will be something like
      return () => {
        contract.off('Transfer', handleTransfer)
      }
    }
    // Add contract and activity to the dependencies array.
  }, [contract, activity, selectedAsset, connectedNetwork, selectedWallet]);
}

我还想指出,删除和重新附加事件侦听器完全没问题。

我假设您想向activity object 添加新交易。我还假设您使用新的 state(当前活动与新交易)在addActionToActivity的某处调用setState 您需要访问最新的活动,但在您的关闭中它不是最新的。

使用setState并传递一个 function 给它,它会收到当前的 state:

setState(prevState => {
  // add transactions to prevState.activity 
  return { ...prevState, activity: {...prevState.activity, transactions: ... }};
});

所以,在你的例子中:

function useQueryEvents() {
  const { state: { connectedNetwork, selectedWallet },
  } = useContext(LocalContext);

  useEffect(() => {
    async function bootstrapQueryEvents() {
      // First get all the past transactions
      const transactions = await getAllPastTransactions();
      const contract = await getContract();

      // Now save them to context state via addActionToActivity
      await addActionToActivity(transactions);

      // Now that all the past transactions have been saved
      // listen to all incoming/outgoing transactions and
      // save to context state via addActionToActivity
      contract.on('Transfer', async (from, to, amount, event) => {
        console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
        const transaction = await formatEventToTransaction(event);
        await addActionToActivity(transaction);
      });
    }

    bootstrapQueryEvents();
  }, [selectedAsset, connectedNetwork, selectedWallet]); // <- I've tried adding `activity` here
}
...
const addActionToActivity = (transactions) => {
  ...
  setState(prevState => {
  // add transactions to prevState.activity 
  return { ...prevState, activity: {...prevState.activity, transactions: ... }};
});
}

使用useRef()来保持和更新活动,使用useState()来管理重新渲染; IE

function useQueryEvents() {
  const [epoch, setEpoch] = useState(0);
  const { current: heap } = useRef({ activity: [], epoch });

  useEffect(() => {
    // Here you can setup your listener, and operate on
    // heap.activity, which will always have an up-to-date
    // list. Also, "heap" is guaranteed to be the same object
    // in each render, thus even it is included into dependencies
    // of useEffect() (e.g. to keep ESLint's Rules of Hooks happy,
    // the useEffect() still fires just once. "setEpoch" is also
    // stable across re-renders, as all state setters.

    // And whenever you decide to re-render the component you do:
    setEpoch(++heap.epoch);
    // Or you can do something smarter, e.g. dumping to the local
    // state the actual stuff you want to render in the text pass.
  }, [heap, setEpoch]);

  return (
    // whatever you need to render
  );
}

您的订阅代码似乎没问题

问题是组件在活动更改时不会更新

当上下文 object 中的某些内容发生更改时,组件不会自动更新。只有当上下文 object 被其他上下文替换时,组件才会更新。

这是包装器,它返回带有.notify()实现的上下文 object。

const UpdatableContext = ({ children }) => {
  // real value
  const [contextValue, setContextValue] = useState({});
  // add notify function
  // also cache result to avoid unnecessary component tree updates
  const mutableContextValue = useMemo(() => {
    let mutableContext = {
      ...contextValue,
      notify() {
        setContextValue(...value);
      },
    }
    return mutableContext;
  }, [contextValue]);
  return <Context.Provider value={mutableContextValue}>
    {children}
  </Context.Provider>;
};

在这样的上下文中更新你想要的任何东西,然后调用.notify()来触发所有依赖组件的更新

我有一个名为useQueryEvents的钩子,它 1) 为用户获取所有过去的交易,2) 监听网络的传入/传出交易。 在这两种情况下,事务都被传递到 function addActionToActivity中,它只是将其附加到活动数组并在关键activity下的上下文 state 中更新它。

我无法让活动正确同步。 每当 state 更新时,它都没有最后一笔交易,因为它总是落后一步。 如果我向依赖项添加activity ,它会起作用,但随后会启动一个新的侦听器(由于使用新的activity值再次调用整个 function),这会导致无限循环,不断切换 state。

function useQueryEvents() {
  const { state: { connectedNetwork, selectedWallet, activity },
  } = useContext(LocalContext);

  useEffect(() => {
    async function bootstrapQueryEvents() {
      // First get all the past transactions
      const transactions = await getAllPastTransactions();
      const contract = await getContract();

      // Now save them to context state via addActionToActivity
      await addActionToActivity(transactions, activity);

      // Now that all the past transactions have been saved
      // listen to all incoming/outgoing transactions and
      // save to context state via addActionToActivity
      contract.on('Transfer', async (from, to, amount, event) => {
        console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
        const transaction = await formatEventToTransaction(event);
        await addActionToActivity(transaction, activity);
      });
    }

    bootstrapQueryEvents();
  }, [selectedAsset, connectedNetwork, selectedWallet]); // <- I've tried adding `activity` here
}

有什么想法可以在启动侦听器的新实例的情况下访问侦听器内部更新的activity值的同时更新 state 吗? 或者也许我可以完全采取不同的方法?

提前致谢

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM