| 68 | * @see {@link https://github.com/coder/coder/pull/10362#discussion_r1380852725} |
| 69 | */ |
| 70 | export async function renderHookWithAuth<Result, Props>( |
| 71 | render: (initialProps: Props) => Result, |
| 72 | config: RenderHookWithAuthConfig<Props>, |
| 73 | ): Promise<RenderHookWithAuthResult<Result, Props>> { |
| 74 | /** |
| 75 | * Our setup here is evil, gross, and cursed because of how React Router |
| 76 | * itself is set up. We need to go through RouterProvider, so that we have a |
| 77 | * Context for calling all the React Router hooks, but that poses two |
| 78 | * problems: |
| 79 | * 1. <RouterProvider> does not accept children, so there is no easy way to |
| 80 | * interface it with renderHook, which uses children as its only tool for |
| 81 | * dependency injection |
| 82 | * 2. Even after you somehow jam a child value into the router, calling |
| 83 | * renderHook's rerender method will not do anything. RouterProvider is |
| 84 | * auto-memoized against re-renders, so because it thinks that its only |
| 85 | * input (the router object) hasn't changed, it will stop the re-render, |
| 86 | * and prevent any children from re-rendering (even if they would have new |
| 87 | * values). |
| 88 | * |
| 89 | * Have to do a lot of work to side-step those issues (best described as a |
| 90 | * "Super Mario warp pipe"), and make sure that we're not relying on internal |
| 91 | * React Router implementation details that could break at a moment's notice |
| 92 | */ |
| 93 | // Some of the let variables are defined with definite assignment (! operator) |
| 94 | let currentLocation!: Location; |
| 95 | const LocationLeaker: FC<PropsWithChildren> = ({ children }) => { |
| 96 | currentLocation = useLocation(); |
| 97 | return <>{children}</>; |
| 98 | }; |
| 99 | |
| 100 | let forceUpdateRenderHookChildren!: () => void; |
| 101 | let currentRenderHookChildren: ReactNode; |
| 102 | |
| 103 | const InitialRoute: FC = () => { |
| 104 | const [, forceRerender] = useReducer((b: boolean) => !b, false); |
| 105 | forceUpdateRenderHookChildren = () => act(forceRerender); |
| 106 | return <LocationLeaker>{currentRenderHookChildren}</LocationLeaker>; |
| 107 | }; |
| 108 | |
| 109 | const { routingOptions = {}, renderOptions = {} } = config; |
| 110 | const { |
| 111 | path = "/", |
| 112 | route = "/", |
| 113 | extraRoutes = [], |
| 114 | nonAuthenticatedRoutes = [], |
| 115 | } = routingOptions; |
| 116 | |
| 117 | const wrappedExtraRoutes = extraRoutes.map((route) => ({ |
| 118 | ...route, |
| 119 | element: <LocationLeaker>{route.element}</LocationLeaker>, |
| 120 | })); |
| 121 | |
| 122 | const wrappedNonAuthRoutes = nonAuthenticatedRoutes.map((route) => ({ |
| 123 | ...route, |
| 124 | element: <LocationLeaker>{route.element}</LocationLeaker>, |
| 125 | })); |
| 126 | |
| 127 | const router = createMemoryRouter( |