react-snapshot-bridge 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # react-snapshot-bridge
2
2
 
3
- > Use React's class-only `getSnapshotBeforeUpdate` lifecycle from **function components**, via a single 30-line wrapper.
3
+ > Use React's class-only `getSnapshotBeforeUpdate` lifecycle from **function components**.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/react-snapshot-bridge.svg)](https://www.npmjs.com/package/react-snapshot-bridge)
6
6
  [![bundle](https://img.shields.io/bundlephobia/minzip/react-snapshot-bridge)](https://bundlephobia.com/package/react-snapshot-bridge)
7
+ [![docs](https://img.shields.io/badge/docs-react--snapshot--bridge-0284c7)](https://react-snapshot-bridge.vercel.app/)
7
8
  [![license](https://img.shields.io/npm/l/react-snapshot-bridge.svg)](./LICENSE)
8
9
 
9
10
  ## Why
@@ -34,16 +35,16 @@ Peer dependency: `react >= 16.3` (when `getSnapshotBeforeUpdate` was introduced)
34
35
  ## Quick start — preserve scroll on prepend
35
36
 
36
37
  ```tsx
37
- import { useRef, useState } from 'react';
38
- import { SnapshotBeforeUpdate } from 'react-snapshot-bridge';
38
+ import { useRef, useState } from "react";
39
+ import { SnapshotBeforeUpdate } from "react-snapshot-bridge";
39
40
 
40
41
  function ChatList() {
41
42
  const scrollerRef = useRef<HTMLDivElement>(null);
42
- const [messages, setMessages] = useState<string[]>(['hi']);
43
+ const [messages, setMessages] = useState<string[]>(["hi"]);
43
44
 
44
45
  return (
45
46
  <>
46
- <button onClick={() => setMessages((m) => ['new message', ...m])}>
47
+ <button onClick={() => setMessages((m) => ["new message", ...m])}>
47
48
  Prepend
48
49
  </button>
49
50
 
@@ -57,7 +58,7 @@ function ChatList() {
57
58
  el.scrollTop = el.scrollHeight - prevDistanceFromBottom;
58
59
  }}
59
60
  >
60
- <div ref={scrollerRef} style={{ height: 200, overflow: 'auto' }}>
61
+ <div ref={scrollerRef} style={{ height: 200, overflow: "auto" }}>
61
62
  {messages.map((msg, i) => (
62
63
  <div key={i}>{msg}</div>
63
64
  ))}
@@ -77,46 +78,50 @@ Without the wrapper, every prepend visually shifts the user's content down. With
77
78
  The example below combines two ideas at once:
78
79
 
79
80
  1. Capture two DOM-derived values (`distanceFromBottom`, `wasAtBottom`) into one snapshot.
80
- 2. Reach into the surrounding component's state via **closure** (`messages.length`) so `apply` can compare "before" with "after" without a separate ref.
81
+ 2. Track the **previous** message count with a ref (synced at the end of `apply`), read the **current** `messages` in `apply` via closure, and read DOM in `capture` before the commit mutates it.
81
82
 
82
83
  ```tsx
83
- import { useRef, useState } from 'react';
84
- import { SnapshotBeforeUpdate } from 'react-snapshot-bridge';
84
+ import { useRef, useState } from "react";
85
+ import { SnapshotBeforeUpdate } from "react-snapshot-bridge";
85
86
 
86
87
  type Snap = {
87
88
  distanceFromBottom: number;
88
89
  wasAtBottom: boolean;
89
- prevCount: number;
90
+ previousMessageCount: number;
90
91
  };
91
92
 
92
93
  function ChatList({ messages }: { messages: string[] }) {
93
94
  const scrollerRef = useRef<HTMLDivElement>(null);
95
+ const prevMessageCountRef = useRef(messages.length);
94
96
 
95
97
  return (
96
98
  <SnapshotBeforeUpdate<Snap>
97
99
  capture={() => {
98
100
  const el = scrollerRef.current!;
99
- const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
101
+ const distanceFromBottom =
102
+ el.scrollHeight - el.scrollTop - el.clientHeight;
100
103
  return {
101
104
  distanceFromBottom,
102
105
  wasAtBottom: distanceFromBottom < 16,
103
- // `messages` here is the value at this commit — by the time `apply`
104
- // runs, the closure inside `apply` will see the new `messages`.
105
- prevCount: messages.length,
106
+ // Still the count from the end of the last `apply` (or initial render).
107
+ previousMessageCount: prevMessageCountRef.current,
106
108
  };
107
109
  }}
108
- apply={({ distanceFromBottom, wasAtBottom, prevCount }) => {
110
+ apply={({ distanceFromBottom, wasAtBottom, previousMessageCount }) => {
109
111
  const el = scrollerRef.current!;
110
- const grewAtTop = messages.length > prevCount;
112
+ const grewAtTop = messages.length > previousMessageCount;
111
113
 
112
114
  if (wasAtBottom) {
113
115
  el.scrollTop = el.scrollHeight; // stick to bottom for new arrivals
114
116
  } else if (grewAtTop) {
115
117
  el.scrollTop = el.scrollHeight - distanceFromBottom; // anchor existing content
116
118
  }
119
+
120
+ // Next `capture` reads this as the "previous" count for that commit.
121
+ prevMessageCountRef.current = messages.length;
117
122
  }}
118
123
  >
119
- <div ref={scrollerRef} style={{ height: 200, overflow: 'auto' }}>
124
+ <div ref={scrollerRef} style={{ height: 200, overflow: "auto" }}>
120
125
  {messages.map((msg, i) => (
121
126
  <div key={i}>{msg}</div>
122
127
  ))}
@@ -126,20 +131,70 @@ function ChatList({ messages }: { messages: string[] }) {
126
131
  }
127
132
  ```
128
133
 
129
- > Why not pass `prevProps` / `prevState` directly into `apply`? Because the wrapper would only see its own internal props (`{ capture, apply, children }`) and an empty state — neither has anything to do with your component. Capturing what you actually need via closure is both safer and clearer.
134
+ > Why not pass `prevProps` / `prevState` directly into `apply`? Because the wrapper would only see its own internal props (`{ capture, apply, children }`) and an empty state — neither has anything to do with your component. Use closure for values you only need after the DOM updates, and a ref when you need a stable "previous" value across commits.
130
135
 
131
136
  ## API
132
137
 
133
- ### `<SnapshotBeforeUpdate<T> capture apply>{children?}</SnapshotBeforeUpdate>`
138
+ ### `<SnapshotBeforeUpdate<T> capture apply enabled?>{children?}</SnapshotBeforeUpdate>`
134
139
 
135
- | Prop | Type | Required | Description |
136
- | ---------- | ----------------------------- | -------- | -------------------------------------------------------------------------------------------- |
137
- | `capture` | `() => T` | yes | Called synchronously **before** DOM mutation. Return value is forwarded to `apply`. |
138
- | `apply` | `(snapshot: T) => void` | yes | Called synchronously **after** DOM mutation, before paint. Receives the value from `capture`. |
139
- | `children` | `React.ReactNode` | no | Optional. When provided, rendered as-is (wrapper pattern). When omitted, renders nothing. |
140
+ | Prop | Type | Required | Description |
141
+ | ---------- | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
142
+ | `capture` | `() => T` | yes | Called synchronously **before** DOM mutation. Return value is forwarded to `apply`. |
143
+ | `apply` | `(snapshot: T) => void` | yes | Called synchronously **after** DOM mutation, before paint. Receives the value from `capture`. |
144
+ | `enabled` | `boolean` | no | Defaults to `true`. When `false`, both `capture` and `apply` are skipped for that commit. Missed updates are not replayed. |
145
+ | `children` | `React.ReactNode` | no | Optional. When provided, rendered as-is (wrapper pattern). When omitted, renders nothing. |
140
146
 
141
147
  The generic `T` is inferred from `capture`'s return type — you usually don't need to specify it.
142
148
 
149
+ ## Where to place it
150
+
151
+ `SnapshotBeforeUpdate` runs its lifecycle **only when its own fiber updates**. So if you want to observe `getSnapshotBeforeUpdate` of a particular component, the bridge has to live **inside that component's render output** — that way, every time the component re-renders, the bridge re-renders with it.
152
+
153
+ Within the same component, both wrapping the DOM as `children` and placing the bridge as a **sibling** of the DOM you care about work perfectly. What does _not_ work reliably is putting the bridge as a sibling of **another component** you want to track: when that component updates on its own (without its parent re-rendering), the bridge will not fire.
154
+
155
+ > The deciding factor is "which component re-renders alongside the bridge", not "wrapper vs sibling".
156
+
157
+ ### Recommended 1 — Wrapper
158
+
159
+ ```tsx
160
+ function ChatList({ messages }: { messages: string[] }) {
161
+ return (
162
+ <SnapshotBeforeUpdate capture={...} apply={...}>
163
+ <div>{/* the DOM you actually care about */}</div>
164
+ </SnapshotBeforeUpdate>
165
+ );
166
+ }
167
+ ```
168
+
169
+ ### Recommended 2 — Sibling of the DOM, inside the same component
170
+
171
+ Useful when wrapping is awkward (e.g. you already have multiple siblings inside a Fragment).
172
+
173
+ ```tsx
174
+ function ChatList({ messages }: { messages: string[] }) {
175
+ return (
176
+ <>
177
+ <div>{/* the DOM you actually care about */}</div>
178
+ <SnapshotBeforeUpdate capture={...} apply={...} />
179
+ </>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ### Not recommended — Sibling of the component you want to track
185
+
186
+ ```tsx
187
+ function Parent() {
188
+ return (
189
+ <>
190
+ <ChatList messages={messages} />
191
+ {/* Wrong: standalone updates inside ChatList may not be picked up */}
192
+ <SnapshotBeforeUpdate capture={...} apply={...} />
193
+ </>
194
+ );
195
+ }
196
+ ```
197
+
143
198
  ## How it works
144
199
 
145
200
  React's commit phase runs in this order:
@@ -158,7 +213,8 @@ The captured value is forwarded to `componentDidUpdate`, which calls `apply`. Fr
158
213
  ## Caveats
159
214
 
160
215
  - **No call on initial mount.** Like the underlying class lifecycle, neither `capture` nor `apply` runs the first time the component appears — only on subsequent updates.
161
- - **Requires a re-render.** If a `React.memo` (or `shouldComponentUpdate`) ancestor short-circuits the render, the bridge will not fire either. Make sure the component is in a tree that actually re-renders when the relevant change happens.
216
+ - **Requires a re-render in the right place.** If a `React.memo` (or `shouldComponentUpdate`) ancestor short-circuits the render, or the bridge is placed as a sibling of a different component that updates on its own, neither callback will fire. See [Where to place it](#where-to-place-it).
217
+ - **`enabled={false}` does not buffer updates.** Updates that occur while disabled are silently dropped. There is no "catch up" call when you re-enable the bridge — the next regular update is what triggers `capture`/`apply` again.
162
218
  - **Concurrent rendering safe.** React 18+ concurrent features may discard render results, but the commit phase itself is synchronous, so `capture`/`apply` always run as a paired set.
163
219
  - **SSR safe.** Lifecycle methods don't run during `renderToString` / `renderToPipeableStream`, so the bridge is a no-op on the server.
164
220
  - **React 19.** Class components and `getSnapshotBeforeUpdate` are not deprecated in React 19. This library remains compatible.
package/dist/index.cjs CHANGED
@@ -3,11 +3,17 @@
3
3
  var react = require('react');
4
4
 
5
5
  // src/SnapshotBeforeUpdate.tsx
6
+ var SKIP = /* @__PURE__ */ Symbol("react-snapshot-bridge-skip");
6
7
  var SnapshotBeforeUpdate = class extends react.Component {
7
8
  getSnapshotBeforeUpdate() {
9
+ if (this.props.enabled === false) {
10
+ return SKIP;
11
+ }
8
12
  return this.props.capture();
9
13
  }
10
14
  componentDidUpdate(_prevProps, _prevState, snapshot) {
15
+ if (this.props.enabled === false) return;
16
+ if (snapshot === SKIP) return;
11
17
  this.props.apply(snapshot);
12
18
  }
13
19
  render() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/SnapshotBeforeUpdate.tsx"],"names":["Component"],"mappings":";;;;;AAkCO,IAAM,oBAAA,GAAN,cAAgDA,eAAA,CAGrD;AAAA,EACS,uBAAA,GAA6B;AACpC,IAAA,OAAO,IAAA,CAAK,MAAM,OAAA,EAAQ;AAAA,EAC5B;AAAA,EAES,kBAAA,CACP,UAAA,EACA,UAAA,EACA,QAAA,EACM;AACN,IAAA,IAAA,CAAK,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,EAC3B;AAAA,EAES,MAAA,GAAoB;AAC3B,IAAA,OAAO,IAAA,CAAK,MAAM,QAAA,IAAY,IAAA;AAAA,EAChC;AACF","file":"index.cjs","sourcesContent":["import { Component } from \"react\";\nimport type { ReactNode } from \"react\";\n\ntype EmptyState = Record<string, never>;\n\nexport interface SnapshotBeforeUpdateProps<T> {\n /**\n * Runs synchronously **before** the DOM is mutated for this commit.\n * Whatever you return here is forwarded to {@link SnapshotBeforeUpdateProps.apply}.\n */\n capture: () => T;\n\n /**\n * Runs synchronously **after** the DOM is mutated, before the browser paints.\n * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.\n */\n apply: (snapshot: T) => void;\n\n /**\n * Optional children. When provided, this component acts as a wrapper and\n * renders its children. When omitted, it renders nothing and you can place\n * it as a sibling next to the elements you care about.\n */\n children?: ReactNode;\n}\n\n/**\n * Bridges React's class-only `getSnapshotBeforeUpdate` lifecycle into trees\n * built with function components.\n *\n * The component re-renders alongside its parent and uses the `getSnapshotBeforeUpdate`\n * / `componentDidUpdate` pair internally to invoke {@link SnapshotBeforeUpdateProps.capture}\n * before DOM mutation and {@link SnapshotBeforeUpdateProps.apply} immediately after.\n */\nexport class SnapshotBeforeUpdate<T = unknown> extends Component<\n SnapshotBeforeUpdateProps<T>,\n EmptyState\n> {\n override getSnapshotBeforeUpdate(): T {\n return this.props.capture();\n }\n\n override componentDidUpdate(\n _prevProps: Readonly<SnapshotBeforeUpdateProps<T>>,\n _prevState: Readonly<EmptyState>,\n snapshot: T,\n ): void {\n this.props.apply(snapshot);\n }\n\n override render(): ReactNode {\n return this.props.children ?? null;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/SnapshotBeforeUpdate.tsx"],"names":["Component"],"mappings":";;;;;AAQA,IAAM,IAAA,0BAAc,4BAA4B,CAAA;AA0CzC,IAAM,oBAAA,GAAN,cAAgDA,eAAA,CAGrD;AAAA,EACS,uBAAA,GAA6B;AACpC,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,OAAA,KAAY,KAAA,EAAO;AAChC,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,MAAM,OAAA,EAAQ;AAAA,EAC5B;AAAA,EAES,kBAAA,CACP,UAAA,EACA,UAAA,EACA,QAAA,EACM;AACN,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,OAAA,KAAY,KAAA,EAAO;AAClC,IAAA,IAAK,aAAiC,IAAA,EAAM;AAE5C,IAAA,IAAA,CAAK,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,EAC3B;AAAA,EAES,MAAA,GAAoB;AAC3B,IAAA,OAAO,IAAA,CAAK,MAAM,QAAA,IAAY,IAAA;AAAA,EAChC;AACF","file":"index.cjs","sourcesContent":["import { Component } from \"react\";\nimport type { ReactNode } from \"react\";\n\ntype EmptyState = Record<string, never>;\n\n/**\n * Symbol used to indicate that the snapshot should be skipped.\n */\nconst SKIP = Symbol(\"react-snapshot-bridge-skip\");\ntype Skip = typeof SKIP;\n\nexport interface SnapshotBeforeUpdateProps<T> {\n /**\n * Runs synchronously **before** the DOM is mutated for this commit.\n * Whatever you return here is forwarded to {@link SnapshotBeforeUpdateProps.apply}.\n */\n capture: () => T;\n\n /**\n * Runs synchronously **after** the DOM is mutated, before the browser paints.\n * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.\n */\n apply: (snapshot: T) => void;\n\n /**\n * When `false`, both `capture` and `apply` are skipped for that commit.\n * Evaluated against the **current** props at commit time, so toggling\n * `true` -> `false` skips both callbacks for that update, and `false` -> `true`\n * runs both as usual on the next update. Defaults to `true`.\n *\n * Updates that occur while disabled are not buffered or replayed later.\n */\n enabled?: boolean;\n\n /**\n * Optional children. When provided, this component acts as a wrapper and\n * renders its children. When omitted, it renders nothing and you can place\n * it as a sibling next to the elements you care about.\n */\n children?: ReactNode;\n}\n\n/**\n * Bridges React's class-only `getSnapshotBeforeUpdate` lifecycle into trees\n * built with function components.\n *\n * The component re-renders alongside its parent and uses the `getSnapshotBeforeUpdate`\n * / `componentDidUpdate` pair internally to invoke {@link SnapshotBeforeUpdateProps.capture}\n * before DOM mutation and {@link SnapshotBeforeUpdateProps.apply} immediately after.\n */\nexport class SnapshotBeforeUpdate<T = unknown> extends Component<\n SnapshotBeforeUpdateProps<T>,\n EmptyState\n> {\n override getSnapshotBeforeUpdate(): T {\n if (this.props.enabled === false) {\n return SKIP as unknown as T;\n }\n return this.props.capture();\n }\n\n override componentDidUpdate(\n _prevProps: Readonly<SnapshotBeforeUpdateProps<T>>,\n _prevState: Readonly<EmptyState>,\n snapshot: T,\n ): void {\n if (this.props.enabled === false) return;\n if ((snapshot as unknown as Skip) === SKIP) return;\n\n this.props.apply(snapshot);\n }\n\n override render(): ReactNode {\n return this.props.children ?? null;\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -12,6 +12,15 @@ interface SnapshotBeforeUpdateProps<T> {
12
12
  * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.
13
13
  */
14
14
  apply: (snapshot: T) => void;
15
+ /**
16
+ * When `false`, both `capture` and `apply` are skipped for that commit.
17
+ * Evaluated against the **current** props at commit time, so toggling
18
+ * `true` -> `false` skips both callbacks for that update, and `false` -> `true`
19
+ * runs both as usual on the next update. Defaults to `true`.
20
+ *
21
+ * Updates that occur while disabled are not buffered or replayed later.
22
+ */
23
+ enabled?: boolean;
15
24
  /**
16
25
  * Optional children. When provided, this component acts as a wrapper and
17
26
  * renders its children. When omitted, it renders nothing and you can place
package/dist/index.d.ts CHANGED
@@ -12,6 +12,15 @@ interface SnapshotBeforeUpdateProps<T> {
12
12
  * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.
13
13
  */
14
14
  apply: (snapshot: T) => void;
15
+ /**
16
+ * When `false`, both `capture` and `apply` are skipped for that commit.
17
+ * Evaluated against the **current** props at commit time, so toggling
18
+ * `true` -> `false` skips both callbacks for that update, and `false` -> `true`
19
+ * runs both as usual on the next update. Defaults to `true`.
20
+ *
21
+ * Updates that occur while disabled are not buffered or replayed later.
22
+ */
23
+ enabled?: boolean;
15
24
  /**
16
25
  * Optional children. When provided, this component acts as a wrapper and
17
26
  * renders its children. When omitted, it renders nothing and you can place
package/dist/index.js CHANGED
@@ -1,11 +1,17 @@
1
1
  import { Component } from 'react';
2
2
 
3
3
  // src/SnapshotBeforeUpdate.tsx
4
+ var SKIP = /* @__PURE__ */ Symbol("react-snapshot-bridge-skip");
4
5
  var SnapshotBeforeUpdate = class extends Component {
5
6
  getSnapshotBeforeUpdate() {
7
+ if (this.props.enabled === false) {
8
+ return SKIP;
9
+ }
6
10
  return this.props.capture();
7
11
  }
8
12
  componentDidUpdate(_prevProps, _prevState, snapshot) {
13
+ if (this.props.enabled === false) return;
14
+ if (snapshot === SKIP) return;
9
15
  this.props.apply(snapshot);
10
16
  }
11
17
  render() {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/SnapshotBeforeUpdate.tsx"],"names":[],"mappings":";;;AAkCO,IAAM,oBAAA,GAAN,cAAgD,SAAA,CAGrD;AAAA,EACS,uBAAA,GAA6B;AACpC,IAAA,OAAO,IAAA,CAAK,MAAM,OAAA,EAAQ;AAAA,EAC5B;AAAA,EAES,kBAAA,CACP,UAAA,EACA,UAAA,EACA,QAAA,EACM;AACN,IAAA,IAAA,CAAK,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,EAC3B;AAAA,EAES,MAAA,GAAoB;AAC3B,IAAA,OAAO,IAAA,CAAK,MAAM,QAAA,IAAY,IAAA;AAAA,EAChC;AACF","file":"index.js","sourcesContent":["import { Component } from \"react\";\nimport type { ReactNode } from \"react\";\n\ntype EmptyState = Record<string, never>;\n\nexport interface SnapshotBeforeUpdateProps<T> {\n /**\n * Runs synchronously **before** the DOM is mutated for this commit.\n * Whatever you return here is forwarded to {@link SnapshotBeforeUpdateProps.apply}.\n */\n capture: () => T;\n\n /**\n * Runs synchronously **after** the DOM is mutated, before the browser paints.\n * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.\n */\n apply: (snapshot: T) => void;\n\n /**\n * Optional children. When provided, this component acts as a wrapper and\n * renders its children. When omitted, it renders nothing and you can place\n * it as a sibling next to the elements you care about.\n */\n children?: ReactNode;\n}\n\n/**\n * Bridges React's class-only `getSnapshotBeforeUpdate` lifecycle into trees\n * built with function components.\n *\n * The component re-renders alongside its parent and uses the `getSnapshotBeforeUpdate`\n * / `componentDidUpdate` pair internally to invoke {@link SnapshotBeforeUpdateProps.capture}\n * before DOM mutation and {@link SnapshotBeforeUpdateProps.apply} immediately after.\n */\nexport class SnapshotBeforeUpdate<T = unknown> extends Component<\n SnapshotBeforeUpdateProps<T>,\n EmptyState\n> {\n override getSnapshotBeforeUpdate(): T {\n return this.props.capture();\n }\n\n override componentDidUpdate(\n _prevProps: Readonly<SnapshotBeforeUpdateProps<T>>,\n _prevState: Readonly<EmptyState>,\n snapshot: T,\n ): void {\n this.props.apply(snapshot);\n }\n\n override render(): ReactNode {\n return this.props.children ?? null;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/SnapshotBeforeUpdate.tsx"],"names":[],"mappings":";;;AAQA,IAAM,IAAA,0BAAc,4BAA4B,CAAA;AA0CzC,IAAM,oBAAA,GAAN,cAAgD,SAAA,CAGrD;AAAA,EACS,uBAAA,GAA6B;AACpC,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,OAAA,KAAY,KAAA,EAAO;AAChC,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,MAAM,OAAA,EAAQ;AAAA,EAC5B;AAAA,EAES,kBAAA,CACP,UAAA,EACA,UAAA,EACA,QAAA,EACM;AACN,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,OAAA,KAAY,KAAA,EAAO;AAClC,IAAA,IAAK,aAAiC,IAAA,EAAM;AAE5C,IAAA,IAAA,CAAK,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,EAC3B;AAAA,EAES,MAAA,GAAoB;AAC3B,IAAA,OAAO,IAAA,CAAK,MAAM,QAAA,IAAY,IAAA;AAAA,EAChC;AACF","file":"index.js","sourcesContent":["import { Component } from \"react\";\nimport type { ReactNode } from \"react\";\n\ntype EmptyState = Record<string, never>;\n\n/**\n * Symbol used to indicate that the snapshot should be skipped.\n */\nconst SKIP = Symbol(\"react-snapshot-bridge-skip\");\ntype Skip = typeof SKIP;\n\nexport interface SnapshotBeforeUpdateProps<T> {\n /**\n * Runs synchronously **before** the DOM is mutated for this commit.\n * Whatever you return here is forwarded to {@link SnapshotBeforeUpdateProps.apply}.\n */\n capture: () => T;\n\n /**\n * Runs synchronously **after** the DOM is mutated, before the browser paints.\n * Receives the value previously returned from {@link SnapshotBeforeUpdateProps.capture}.\n */\n apply: (snapshot: T) => void;\n\n /**\n * When `false`, both `capture` and `apply` are skipped for that commit.\n * Evaluated against the **current** props at commit time, so toggling\n * `true` -> `false` skips both callbacks for that update, and `false` -> `true`\n * runs both as usual on the next update. Defaults to `true`.\n *\n * Updates that occur while disabled are not buffered or replayed later.\n */\n enabled?: boolean;\n\n /**\n * Optional children. When provided, this component acts as a wrapper and\n * renders its children. When omitted, it renders nothing and you can place\n * it as a sibling next to the elements you care about.\n */\n children?: ReactNode;\n}\n\n/**\n * Bridges React's class-only `getSnapshotBeforeUpdate` lifecycle into trees\n * built with function components.\n *\n * The component re-renders alongside its parent and uses the `getSnapshotBeforeUpdate`\n * / `componentDidUpdate` pair internally to invoke {@link SnapshotBeforeUpdateProps.capture}\n * before DOM mutation and {@link SnapshotBeforeUpdateProps.apply} immediately after.\n */\nexport class SnapshotBeforeUpdate<T = unknown> extends Component<\n SnapshotBeforeUpdateProps<T>,\n EmptyState\n> {\n override getSnapshotBeforeUpdate(): T {\n if (this.props.enabled === false) {\n return SKIP as unknown as T;\n }\n return this.props.capture();\n }\n\n override componentDidUpdate(\n _prevProps: Readonly<SnapshotBeforeUpdateProps<T>>,\n _prevState: Readonly<EmptyState>,\n snapshot: T,\n ): void {\n if (this.props.enabled === false) return;\n if ((snapshot as unknown as Skip) === SKIP) return;\n\n this.props.apply(snapshot);\n }\n\n override render(): ReactNode {\n return this.props.children ?? null;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-snapshot-bridge",
3
- "version": "1.0.0",
4
- "description": "Alternative GetSnapshotBeforeUpdate Hook for React Functional Components",
3
+ "version": "1.1.0",
4
+ "description": "Use React's class-only `getSnapshotBeforeUpdate` lifecycle from function components.",
5
5
  "keywords": [
6
6
  "react",
7
7
  "getSnapshotBeforeUpdate",