hookOrDefault.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import {ComponentProps, lazy, Suspense, useEffect, useState} from 'react';
  2. import HookStore from 'sentry/stores/hookStore';
  3. import {HookName, Hooks} from 'sentry/types/hooks';
  4. interface Params<H extends HookName> {
  5. /**
  6. * The name of the hook as listed in hookstore.add(hookName, callback)
  7. */
  8. hookName: H;
  9. /**
  10. * Component that will be shown if no hook is available
  11. */
  12. defaultComponent?: ReturnType<Hooks[H]>;
  13. /**
  14. * This is a function that returns a promise (more specifically a function
  15. * that returns the result of a dynamic import using `import()`. This will
  16. * use React.Suspense and React.lazy to render the component.
  17. */
  18. defaultComponentPromise?: () => Promise<ReturnType<Hooks[H]>>;
  19. }
  20. /**
  21. * Use this instead of the usual ternery operator when using getsentry hooks.
  22. * So in lieu of:
  23. *
  24. * HookStore.get('component:org-auth-view').length
  25. * ? HookStore.get('component:org-auth-view')[0]()
  26. * : OrganizationAuth
  27. *
  28. * do this instead:
  29. *
  30. * const HookedOrganizationAuth = HookOrDefault({
  31. * hookName:'component:org-auth-view',
  32. * defaultComponent: OrganizationAuth,
  33. * })
  34. *
  35. * Note, you will need to add the hookstore function in getsentry [0] first and
  36. * then register the types [2] and validHookName [1] in sentry.
  37. *
  38. * [0] /getsentry/static/getsentry/gsApp/registerHooks.jsx
  39. * [1] /sentry/app/stores/hookStore.tsx
  40. * [2] /sentry/app/types/hooks.ts
  41. */
  42. function HookOrDefault<H extends HookName>({
  43. hookName,
  44. defaultComponent,
  45. defaultComponentPromise,
  46. }: Params<H>): React.FunctionComponent<ComponentProps<ReturnType<Hooks[H]>>> {
  47. type Props = ComponentProps<ReturnType<Hooks[H]>>;
  48. // Defining the props here is unnecessary and slow for typescript
  49. function getDefaultComponent(): React.ComponentType<any> | undefined {
  50. // If `defaultComponentPromise` is passed, then return a Suspended component
  51. if (defaultComponentPromise) {
  52. // Lazy adds a complicated type that is not important
  53. const DefaultComponent: React.ComponentType<any> = lazy(defaultComponentPromise);
  54. return function (props: Props) {
  55. return (
  56. <Suspense fallback={null}>
  57. <DefaultComponent {...props} />
  58. </Suspense>
  59. );
  60. };
  61. }
  62. return defaultComponent;
  63. }
  64. function HookOrDefaultComponent(props: Props) {
  65. const [hooks, setHooks] = useState<Hooks[H][]>(HookStore.get(hookName));
  66. useEffect(() => {
  67. const unsubscribe = HookStore.listen((name: string, newHooks: Hooks[H][]) => {
  68. if (name === hookName) {
  69. setHooks(newHooks);
  70. }
  71. }, undefined);
  72. return () => {
  73. unsubscribe();
  74. };
  75. }, []);
  76. const hookExists = hooks && hooks.length;
  77. const componentFromHook = hooks[0]?.();
  78. // Defining the props here is unnecessary and slow for typescript
  79. const HookComponent: React.ComponentType<any> =
  80. hookExists && componentFromHook ? componentFromHook : getDefaultComponent();
  81. if (!HookComponent) {
  82. return null;
  83. }
  84. return <HookComponent {...props} />;
  85. }
  86. HookOrDefaultComponent.displayName = `HookOrDefaultComponent(${hookName})`;
  87. return HookOrDefaultComponent;
  88. }
  89. export default HookOrDefault;