Source: src/components/useTracker.js

import { useEffect, useRef, useReducer, useMemo } from 'react';
import Tracker from '../Tracker.js';

/** @private */
const increment = (x) => x + 1;
/** @private */
const useForceUpdate = () => useReducer(increment, 0)[1];

/**
 * Hook that re-runs any time a reactive data is changed.
 * TODO: make work with async functions.
 *
 * Reactive data sources that trigger a re-run are, for example:
 * - ReactiveDict.prototype.get
 * - Meteor.user() calls
 * - Meteor.status() calls
 * - subscription handles
 *
 * @param trackerFn {function}
 * @param deps
 * @returns {null}
 */
export default (trackerFn, deps = []) => {
  const { current: refs } = useRef({
    data: null,
    meteorDataDep: new Tracker.Dependency(),
    trackerFn: trackerFn,
    computation: null,
    isMounted: true,
  });
  const forceUpdate = useForceUpdate();
  refs.trackerFn = trackerFn;

  useMemo(() => {
    if (refs.computation) {
      refs.computation.stop();
      refs.computation = null;
    }
    Tracker.nonreactive(() => {
      Tracker.autorun((currentComputation) => {
        if (refs.isMounted) {
          refs.computation = currentComputation;
          refs.data = trackerFn();
          forceUpdate();
        } else {
          refs.computation?.stop();
        }
      });
    });
  }, deps);

  useEffect(() => {
    return () => {
      refs.isMounted = false;
      refs.computation?.stop();
      refs.computation = null;
    };
  }, []);

  return refs.data;
};