|
| 1 | +import { useMemo } from 'react'; |
1 | 2 | import type { Decorator } from '@storybook/react-vite'; |
2 | 3 | import type { ComponentType } from 'react'; |
3 | 4 | import { |
@@ -30,27 +31,40 @@ interface RemixStubOptions { |
30 | 31 | export const withReactRouterStubDecorator = (options: RemixStubOptions): Decorator => { |
31 | 32 | const { routes, initialPath = '/' } = options; |
32 | 33 |
|
| 34 | + // We define the Stub component outside the return function to ensure it's not recreated |
| 35 | + // on every render of the Story component itself. |
| 36 | + const CachedStub: ComponentType<{ initialEntries?: string[] }> | null = null; |
| 37 | + const lastMappedRoutes: StubRouteObject[] | null = null; |
| 38 | + |
33 | 39 | return (Story, context) => { |
34 | 40 | // Map routes to include the Story component if no Component is provided |
35 | | - const mappedRoutes = routes.map((route) => ({ |
36 | | - ...route, |
37 | | - Component: route.Component ?? (() => <Story {...context.args} />), |
38 | | - })); |
| 41 | + const mappedRoutes = useMemo( |
| 42 | + () => |
| 43 | + routes.map((route) => ({ |
| 44 | + ...route, |
| 45 | + Component: route.Component ?? Story, |
| 46 | + })), |
| 47 | + [Story], |
| 48 | + ); |
39 | 49 |
|
40 | 50 | // Get the base path (without existing query params from options) |
41 | | - const basePath = initialPath.split('?')[0]; |
| 51 | + const basePath = useMemo(() => initialPath.split('?')[0], []); |
42 | 52 |
|
43 | 53 | // Get the current search string from the actual browser window, if available |
44 | 54 | // If not available, use a default search string with parameters needed for the data table |
45 | 55 | const currentWindowSearch = typeof window !== 'undefined' ? window.location.search : '?page=0&pageSize=10'; |
46 | 56 |
|
47 | 57 | // Combine them for the initial entry |
48 | | - const actualInitialPath = `${basePath}${currentWindowSearch}`; |
| 58 | + const actualInitialPath = useMemo(() => `${basePath}${currentWindowSearch}`, [basePath, currentWindowSearch]); |
49 | 59 |
|
50 | 60 | // Use React Router's official createRoutesStub |
51 | | - const Stub = createRoutesStub(mappedRoutes); |
| 61 | + // We memoize the Stub component to prevent unnecessary remounts of the entire story |
| 62 | + // when the decorator re-renders. |
| 63 | + const Stub = useMemo(() => createRoutesStub(mappedRoutes), [mappedRoutes]); |
| 64 | + |
| 65 | + const initialEntries = useMemo(() => [actualInitialPath], [actualInitialPath]); |
52 | 66 |
|
53 | | - return <Stub initialEntries={[actualInitialPath]} />; |
| 67 | + return <Stub initialEntries={initialEntries} />; |
54 | 68 | }; |
55 | 69 | }; |
56 | 70 |
|
|
0 commit comments