Skip to content

Commit

Permalink
fix(perf): release references to node styles
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed Nov 20, 2024
1 parent 9054ca4 commit c794e08
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/old-flowers-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@zag-js/presence": patch
"@zag-js/collapsible": patch
---

Fix memory leak where machine might hold on to the element and it's styles after element is unmounted
7 changes: 5 additions & 2 deletions .xstate/collapsible.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const fetchMachine = createMachine({
"isOpenControlled": false,
"isOpenControlled": false
},
exit: ["clearInitial"],
exit: ["clearInitial", "cleanupNode"],
on: {
UPDATE_CONTEXT: {
actions: "updateContext"
Expand Down Expand Up @@ -74,7 +74,10 @@ const fetchMachine = createMachine({
}, {
target: "closing",
actions: ["setInitial", "computeSize", "invokeOnClose"]
}]
}],
"SIZE.MEASURE": {
actions: ["measureSize"]
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion .xstate/presence.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
const fetchMachine = createMachine({
initial: ctx.present ? "mounted" : "unmounted",
context: {},
exit: ["clearInitial"],
exit: ["clearInitial", "cleanupNode"],
on: {
"NODE.SET": {
actions: ["setNode", "setStyles"]
Expand Down
15 changes: 8 additions & 7 deletions examples/next-ts/components/toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { dataAttr } from "@zag-js/dom-query"
import { ReactNode, useState } from "react"

type ToolbarProps = {
type ToolbarProps = React.HTMLAttributes<HTMLDivElement> & {
controls?: null | (() => JSX.Element)
children: ReactNode
viz?: boolean
}

export function Toolbar(props: ToolbarProps) {
const [active, setActive] = useState(props.viz ? 1 : !props.controls ? 1 : 0)
const { controls, children, viz, ...rest } = props
const [active, setActive] = useState(viz ? 1 : !controls ? 1 : 0)

return (
<div className="toolbar">
<div className="toolbar" {...rest}>
<nav>
{props.controls && (
{controls && (
<button data-active={dataAttr(active === 0)} onClick={() => setActive(0)}>
Controls
</button>
Expand All @@ -23,13 +24,13 @@ export function Toolbar(props: ToolbarProps) {
</button>
</nav>
<div>
{props.controls && (
{controls && (
<div data-content data-active={dataAttr(active === 0)}>
<props.controls />
{controls()}
</div>
)}
<div data-content data-active={dataAttr(active === 1)}>
{props.children}
{children}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/next-ts/pages/compositions
4 changes: 4 additions & 0 deletions packages/machines/collapsible/src/collapsible.connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
disabled,
visible,
open,
measureSize() {
send("SIZE.MEASURE")
},
setOpen(nextOpen) {
if (nextOpen === open) return
send(nextOpen ? "OPEN" : "CLOSE")
Expand All @@ -35,6 +38,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
getContentProps() {
return normalize.element({
...parts.content.attrs,
"data-collapsible": "",
"data-state": skip ? undefined : open ? "open" : "closed",
id: dom.getContentId(state.context),
"data-disabled": dataAttr(disabled),
Expand Down
15 changes: 14 additions & 1 deletion packages/machines/collapsible/src/collapsible.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function machine(userContext: UserDefinedContext) {
open: ["setInitial", "computeSize", "toggleVisibility"],
},

exit: ["clearInitial"],
exit: ["clearInitial", "cleanupNode"],

states: {
closed: {
Expand Down Expand Up @@ -91,6 +91,9 @@ export function machine(userContext: UserDefinedContext) {
actions: ["setInitial", "computeSize", "invokeOnClose"],
},
],
"SIZE.MEASURE": {
actions: ["measureSize"],
},
},
},
},
Expand Down Expand Up @@ -145,6 +148,16 @@ export function machine(userContext: UserDefinedContext) {
clearInitial(ctx) {
ctx.initial = false
},
cleanupNode(ctx) {
ctx.stylesRef = null
},
measureSize(ctx) {
const contentEl = dom.getContentEl(ctx)
if (!contentEl) return
const { height, width } = contentEl.getBoundingClientRect()
ctx.height = height
ctx.width = width
},
computeSize(ctx, evt) {
ctx._rafCleanup?.()

Expand Down
4 changes: 4 additions & 0 deletions packages/machines/collapsible/src/collapsible.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
* Function to open or close the collapsible.
*/
setOpen(open: boolean): void
/**
* Function to measure the size of the content.
*/
measureSize(): void

getRootProps(): T["element"]
getTriggerProps(): T["button"]
Expand Down
6 changes: 5 additions & 1 deletion packages/machines/presence/src/presence.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function machine(ctx: Partial<UserDefinedContext>) {
...ctx,
},

exit: ["clearInitial"],
exit: ["clearInitial", "cleanupNode"],

watch: {
present: ["setInitial", "syncPresence"],
Expand Down Expand Up @@ -83,6 +83,10 @@ export function machine(ctx: Partial<UserDefinedContext>) {
clearInitial(ctx) {
ctx.initial = false
},
cleanupNode(ctx) {
ctx.node = null
ctx.styles = null
},
invokeOnExitComplete(ctx) {
ctx.onExitComplete?.()
},
Expand Down

0 comments on commit c794e08

Please sign in to comment.