withInternalFallback.tsx 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import { atom, useAtom } from "jotai";
  2. import React, { useLayoutEffect } from "react";
  3. export const withInternalFallback = <P,>(
  4. componentName: string,
  5. Component: React.FC<P>,
  6. ) => {
  7. const counterAtom = atom(0);
  8. // flag set on initial render to tell the fallback component to skip the
  9. // render until mount counter are initialized. This is because the counter
  10. // is initialized in an effect, and thus we could end rendering both
  11. // components at the same time until counter is initialized.
  12. let preferHost = false;
  13. const WrapperComponent: React.FC<
  14. P & {
  15. __fallback?: boolean;
  16. }
  17. > = (props) => {
  18. const [counter, setCounter] = useAtom(counterAtom);
  19. useLayoutEffect(() => {
  20. setCounter((counter) => counter + 1);
  21. return () => {
  22. setCounter((counter) => counter - 1);
  23. };
  24. }, [setCounter]);
  25. if (!props.__fallback) {
  26. preferHost = true;
  27. }
  28. // ensure we don't render fallback and host components at the same time
  29. if (
  30. // either before the counters are initialized
  31. (!counter && props.__fallback && preferHost) ||
  32. // or after the counters are initialized, and both are rendered
  33. // (this is the default when host renders as well)
  34. (counter > 1 && props.__fallback)
  35. ) {
  36. return null;
  37. }
  38. return <Component {...props} />;
  39. };
  40. WrapperComponent.displayName = componentName;
  41. return WrapperComponent;
  42. };