useLegacyStore.tsx 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. import {useEffect, useRef, useState} from 'react';
  2. import {Store} from 'reflux';
  3. import {CommonStoreDefinition} from './types';
  4. interface LegacyStoreShape extends Store, CommonStoreDefinition<any> {}
  5. /**
  6. * This wrapper exists because we have many old-style enzyme tests that trigger
  7. * updates to stores without being wrapped in act.
  8. *
  9. * Wrting tests with React Testing Library typically circumvents the need for
  10. * this. See [0].
  11. *
  12. * [0]: https://javascript.plainenglish.io/you-probably-dont-need-act-in-your-react-tests-2a0bcd2ad65c
  13. */
  14. window._legacyStoreHookUpdate = update => update();
  15. /**
  16. * Returns the state of a reflux store. Automatically unsubscribes when destroyed
  17. *
  18. * ```
  19. * const teams = useLegacyStore(TeamStore);
  20. * ```
  21. */
  22. export function useLegacyStore<T extends LegacyStoreShape>(
  23. store: T
  24. ): ReturnType<T['getState']> {
  25. const [state, setState] = useState(store.getState());
  26. // Not all stores emit the new state, call get on change
  27. const callback = () => window._legacyStoreHookUpdate(() => setState(store.getState()));
  28. // If we setup the listener in useEffect, there is a small race condition
  29. // where the store may emit an event before we're listening (since useEffect
  30. // fires AFTER rendering). Avoid this by ensuring we start listening
  31. // *immediately* after we initialize the useState.
  32. const unlisten = useRef<Function>();
  33. if (unlisten.current === undefined) {
  34. unlisten.current = store.listen(callback, undefined);
  35. }
  36. useEffect(() => {
  37. return () => void unlisten.current?.();
  38. }, []);
  39. return state;
  40. }