Skip to content

Commit ac2c476

Browse files
authored
feat: improve perf (#246)
* feat: improve perf * add memo for better perf, hide until open * add memo for better perf, hide until open * fix issue with statenodes * fix issues * additional perf improvements * revert provider location * version bump
1 parent 98b5df5 commit ac2c476

File tree

17 files changed

+189
-168
lines changed

17 files changed

+189
-168
lines changed

packages/react-router-devtools/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "react-router-devtools",
33
"description": "Devtools for React Router - debug, trace, find hydration errors, catch bugs and inspect server/client data with react-router-devtools",
44
"author": "Alem Tuzlak",
5-
"version": "6.1.0",
5+
"version": "6.2.0",
66
"license": "MIT",
77
"keywords": [
88
"react-router",
@@ -131,6 +131,7 @@
131131
"@babel/traverse": "^7.28.5",
132132
"@babel/types": "^7.28.5",
133133
"@radix-ui/react-accordion": "^1.2.12",
134+
"@tanstack/devtools-client": "^0.0.5",
134135
"@tanstack/devtools-event-client": "^0.4.0",
135136
"@tanstack/devtools-vite": "^0.4.1",
136137
"@tanstack/react-devtools": "^0.9.1",
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { memo } from "react"
12
import { useStyles } from "../styles/use-styles.js"
23

34
interface TabContentProps {
45
children: React.ReactNode
56
}
67

7-
export const TabContent = ({ children }: TabContentProps) => {
8+
export const TabContent = memo(({ children }: TabContentProps) => {
89
const { styles } = useStyles()
910
return <div className={styles.tabContent.container}>{children}</div>
10-
}
11+
})

packages/react-router-devtools/src/client/components/Tag.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ReactNode } from "react"
1+
import { type ReactNode, memo } from "react"
22
import { cx, useStyles } from "../styles/use-styles.js"
33

44
export const TAG_COLORS = {
@@ -16,7 +16,7 @@ interface TagProps {
1616
size?: "small" | "default"
1717
}
1818

19-
const Tag = ({ color, children, className, size = "default" }: TagProps) => {
19+
const Tag = memo(({ color, children, className, size = "default" }: TagProps) => {
2020
const { styles } = useStyles()
2121
return (
2222
<span
@@ -30,6 +30,6 @@ const Tag = ({ color, children, className, size = "default" }: TagProps) => {
3030
{children}
3131
</span>
3232
)
33-
}
33+
})
3434

3535
export { Tag }

packages/react-router-devtools/src/client/components/icon/Icon.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SVGProps } from "react"
1+
import { type SVGProps, memo } from "react"
22
import { cx } from "../../styles/use-styles.js"
33
import { useStyles } from "../../styles/use-styles.js"
44
import type { IconName } from "./icons/types.js"
@@ -30,7 +30,7 @@ const strokeIcon: Partial<IconName>[] = []
3030
* Icon component wrapper for SVG icons.
3131
* @returns SVG icon as a react component
3232
*/
33-
export const Icon = ({ name, title, testId, className, size = "sm", ...props }: IconProps) => {
33+
export const Icon = memo(({ name, title, testId, className, size = "sm", ...props }: IconProps) => {
3434
const { styles } = useStyles()
3535
const iconSize = IconSize[size]
3636
const isEmptyFill = emptyFill.includes(name)
@@ -316,4 +316,4 @@ export const Icon = ({ name, title, testId, className, size = "sm", ...props }:
316316
<use href={`#${name}`} />
317317
</svg>
318318
)
319-
}
319+
})

packages/react-router-devtools/src/client/components/jsonRenderer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const isPromise = (value: any): value is Promise<any> => {
1414
return value && typeof value.then === "function"
1515
}
1616

17-
const JsonRendererComponent = ({ data, expansionLevel }: JsonRendererProps) => {
17+
const JsonRendererComponent = memo(({ data, expansionLevel }: JsonRendererProps) => {
1818
const { styles } = useStyles()
1919
const { settings } = useSettingsContext()
2020
const ref = useRef(true)
@@ -71,7 +71,7 @@ const JsonRendererComponent = ({ data, expansionLevel }: JsonRendererProps) => {
7171
return (
7272
<JsonView highlightUpdates style={customTheme} collapsed={expansionLevel ?? settings.expansionLevel} value={json} />
7373
)
74-
}
74+
})
7575

7676
const JsonRenderer = memo(JsonRendererComponent)
7777

packages/react-router-devtools/src/client/embedded-dev-tools.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import clsx from "clsx"
2-
import { useEffect, useState } from "react"
2+
import { memo, useEffect, useState } from "react"
33
import { RDTContextProvider } from "./context/RDTContext.js"
44
import { useFindRouteOutlets } from "./hooks/useReactTreeListeners.js"
55
import { useSetRouteBoundaries } from "./hooks/useSetRouteBoundaries.js"
@@ -10,18 +10,25 @@ import { Tabs } from "./layout/Tabs.js"
1010
import type { ReactRouterDevtoolsProps } from "./react-router-dev-tools.js"
1111
// Import to ensure global reset styles are injected
1212
import "./styles/use-styles.js"
13+
import { devtoolsEventClient } from "@tanstack/devtools-client"
1314
import { RequestProvider } from "./context/requests/request-context.js"
1415
import { REACT_ROUTER_DEV_TOOLS } from "./utils/storage.js"
15-
1616
export interface EmbeddedDevToolsProps extends ReactRouterDevtoolsProps {
1717
mainPanelClassName?: string
1818
className?: string
1919
}
20-
const Embedded = ({ mainPanelClassName, className }: EmbeddedDevToolsProps) => {
20+
const Embedded = memo(({ mainPanelClassName, className }: EmbeddedDevToolsProps) => {
2121
useTimelineHandler()
2222
useFindRouteOutlets()
2323
useSetRouteBoundaries()
2424

25+
const [isOpen, setIsOpen] = useState(true)
26+
useEffect(() => {
27+
const cleanup = devtoolsEventClient.on("trigger-toggled", (e) => {
28+
setIsOpen(e.payload.isOpen)
29+
})
30+
return cleanup
31+
}, [])
2532
return (
2633
<div
2734
id={REACT_ROUTER_DEV_TOOLS}
@@ -30,13 +37,15 @@ const Embedded = ({ mainPanelClassName, className }: EmbeddedDevToolsProps) => {
3037
}}
3138
className={clsx("react-router-dev-tools", "h-full flex-row w-full", className)}
3239
>
33-
<MainPanel className={mainPanelClassName} isEmbedded isOpen={true}>
34-
<Tabs />
35-
<ContentPanel />
36-
</MainPanel>
40+
{isOpen ? (
41+
<MainPanel className={mainPanelClassName} isEmbedded isOpen={true}>
42+
<Tabs />
43+
<ContentPanel />
44+
</MainPanel>
45+
) : null}
3746
</div>
3847
)
39-
}
48+
})
4049

4150
let hydrating = true
4251

packages/react-router-devtools/src/client/hooks/useReactTreeListeners.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function useFindRouteOutlets() {
2121
const styleNearestElement = useCallback((fiberNode: any) => {
2222
if (!fiberNode) return
2323

24-
if (fiberNode.stateNode) {
24+
if (typeof fiberNode?.stateNode?.classList?.add === "function") {
2525
return fiberNode.stateNode.classList.add(ROUTE_CLASS)
2626
}
2727
styleNearestElement(fiberNode.child)

packages/react-router-devtools/src/client/layout/ContentPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Fragment } from "react"
1+
import { Fragment, memo } from "react"
22
import { useTabs } from "../hooks/useTabs.js"
33
import { cx } from "../styles/use-styles.js"
44
import { useStyles } from "../styles/use-styles.js"
55
import { TimelineTab } from "../tabs/TimelineTab.js"
66

7-
const ContentPanel = () => {
7+
const ContentPanel = memo(() => {
88
const { Component, hideTimeline, activeTab } = useTabs()
99
const { styles } = useStyles()
1010

@@ -29,6 +29,6 @@ const ContentPanel = () => {
2929
)}
3030
</div>
3131
)
32-
}
32+
})
3333

3434
export { ContentPanel }

packages/react-router-devtools/src/client/layout/MainPanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { memo } from "react"
12
import { cx } from "../styles/use-styles.js"
23
import { useStyles } from "../styles/use-styles.js"
34

@@ -8,7 +9,7 @@ interface MainPanelProps {
89
className?: string
910
}
1011

11-
const MainPanel = ({ children, isOpen, className }: MainPanelProps) => {
12+
const MainPanel = memo(({ children, isOpen, className }: MainPanelProps) => {
1213
const { styles } = useStyles()
1314

1415
return (
@@ -26,6 +27,6 @@ const MainPanel = ({ children, isOpen, className }: MainPanelProps) => {
2627
{children}
2728
</div>
2829
)
29-
}
30+
})
3031

3132
export { MainPanel }
Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { memo } from "react"
12
import { useSettingsContext } from "../context/useRDTContext.js"
23
import { useHorizontalScroll } from "../hooks/useHorizontalScroll.js"
34
import { useTabs } from "../hooks/useTabs.js"
@@ -11,39 +12,41 @@ declare global {
1112
}
1213
}
1314

14-
const Tab = ({
15-
tab,
16-
activeTab,
17-
className,
18-
onClick,
19-
}: {
20-
tab: TabType
21-
activeTab?: string
22-
className?: string
23-
onClick?: () => void
24-
}) => {
25-
const { setSettings } = useSettingsContext()
26-
const { styles } = useStyles()
15+
const Tab = memo(
16+
({
17+
tab,
18+
activeTab,
19+
className,
20+
onClick,
21+
}: {
22+
tab: TabType
23+
activeTab?: string
24+
className?: string
25+
onClick?: () => void
26+
}) => {
27+
const { setSettings } = useSettingsContext()
28+
const { styles } = useStyles()
2729

28-
return (
29-
<button
30-
data-testid={tab.id}
31-
onClick={() => (onClick ? onClick() : setSettings({ activeTab: tab.id as TabsType }))}
32-
title={typeof tab.name === "string" ? tab.name : undefined}
33-
type="button"
34-
className={cx(
35-
"group",
36-
styles.layout.tabs.tab,
37-
activeTab !== tab.id && styles.layout.tabs.tabInactive,
38-
activeTab === tab.id && styles.layout.tabs.tabActive
39-
)}
40-
>
41-
<div className={cx(className, styles.layout.tabs.tabIcon)}>{tab.icon}</div>
42-
</button>
43-
)
44-
}
30+
return (
31+
<button
32+
data-testid={tab.id}
33+
onClick={() => (onClick ? onClick() : setSettings({ activeTab: tab.id as TabsType }))}
34+
title={typeof tab.name === "string" ? tab.name : undefined}
35+
type="button"
36+
className={cx(
37+
"group",
38+
styles.layout.tabs.tab,
39+
activeTab !== tab.id && styles.layout.tabs.tabInactive,
40+
activeTab === tab.id && styles.layout.tabs.tabActive
41+
)}
42+
>
43+
<div className={cx(className, styles.layout.tabs.tabIcon)}>{tab.icon}</div>
44+
</button>
45+
)
46+
}
47+
)
4548

46-
const Tabs = () => {
49+
const Tabs = memo(() => {
4750
const { settings } = useSettingsContext()
4851
const { styles } = useStyles()
4952
const { activeTab } = settings
@@ -59,6 +62,6 @@ const Tabs = () => {
5962
</div>
6063
</div>
6164
)
62-
}
65+
})
6366

6467
export { Tabs }

0 commit comments

Comments
 (0)