hookOrDefault.tsx 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import {Component, lazy, Suspense} from 'react';
  2. import HookStore from 'sentry/stores/hookStore';
  3. import {HookName, Hooks} from 'sentry/types/hooks';
  4. type 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>) {
  47. type Props = React.ComponentProps<ReturnType<Hooks[H]>>;
  48. type State = {hooks: Hooks[H][]};
  49. class HookOrDefaultComponent extends Component<Props, State> {
  50. static displayName = `HookOrDefaultComponent(${hookName})`;
  51. state: State = {
  52. hooks: HookStore.get(hookName),
  53. };
  54. componentWillUnmount() {
  55. this.unlistener?.();
  56. }
  57. unlistener = HookStore.listen(
  58. (name: string, hooks: Hooks[HookName][]) =>
  59. name === hookName && this.setState({hooks}),
  60. undefined
  61. );
  62. get defaultComponent() {
  63. // If `defaultComponentPromise` is passed, then return a Suspended component
  64. if (defaultComponentPromise) {
  65. const DefaultComponent = lazy(defaultComponentPromise);
  66. return (props: Props) => (
  67. <Suspense fallback={null}>
  68. <DefaultComponent {...props} />
  69. </Suspense>
  70. );
  71. }
  72. return defaultComponent;
  73. }
  74. render() {
  75. const hookExists = this.state.hooks && this.state.hooks.length;
  76. const componentFromHook = this.state.hooks[0]?.();
  77. const HookComponent =
  78. hookExists && componentFromHook ? componentFromHook : this.defaultComponent;
  79. return HookComponent ? <HookComponent {...this.props} /> : null;
  80. }
  81. }
  82. return HookOrDefaultComponent;
  83. }
  84. export default HookOrDefault;