Saturday, 8 May 2021

Update state within listener that is inside useEffect

I have a hook called useQueryEvents that 1) fetches all past transactions for a user and 2) listens to the network for incoming/outgoing transactions. In both cases the transactions are passed into a function addActionToActivity that simply appends it to the activity array and updates it in the context state under the key activity.

I can't get the activity to sync correctly. Whenever the state updates it does not have the last transaction because it's always one step behind. If I add activity to the dependancy it works but then starts a new listener (due to the whole function being called again with the new activity value) which causes an infinity-like-loop which keeps switching up the 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
}

addActionToActivity (redacted unnecessary code) - all it does is do fetch relevant data and appends the newly concatenated transactions array onto activity then calls setAsyncValue (see below for that function)

export async function addActionToActivity(transaction, activity) {
  return new Promise((resolve, reject) => {
    async function handleAdd() {
      const { username, selectedAsset } = getUserInfo();
      const parsedActivity = JSON.parse(activity);
      const { userActivity, assetActivity } = getSpecificActivity();

      // Add the transaction(s) onto the existing activity
      const updatedAssetSpecificActivity = assetActivity.concat(transaction);

      const removedDuplicates = removeDuplicateTransactions(updatedAssetSpecificActivity, hashes);

      const updatedActivity = {
        ...parsedActivity,
        [username]: {
          ...userActivity,
          [selectedAsset]: removedDuplicates,
        },
      };

      try {
        await setAsyncValue('activity', JSON.stringify(updatedActivity));
        resolve();
      } catch {
        reject();
      }
    }
    handleAdd();
  });
}

Local Context Contents (redacted unnecessary code) - setAsyncValue simply stores the activity to local storage then updates the context state using a dispatch from a reducer in the context.

function localReducer(prevState, action) {
  switch (action.type) {
    case "UPDATE_VALUE":
      return {
        ...prevState,
        [action.payload.key]: action.payload.value,
      };
    default:
      return prevState;
  }
}

const [state, dispatch] = useReducer(localReducer, initialLocalState);

function updateValue(key, value, dispatch): {
  dispatch({
    type: 'UPDATE_VALUE',
    payload: { key, value },
  });
}

const localContext = useMemo(() => ({
  state,
  dispatch,
  setAsyncValue: async (key: string, value: string) => {
    await storeAsyncData(`@${key}`, value);
    updateValue(key, value, dispatch);
  },
}));

Any ideas how I can approach updating the state while having access to the updated activity value inside the listener without starting a new instance of the listener? Or maybe there's a different approach I can take altogether?

Thanks in advance



from Update state within listener that is inside useEffect

No comments:

Post a Comment