why-render-react 0.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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/diff.d.ts +34 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +55 -0
- package/dist/diff.js.map +1 -0
- package/dist/global/index.t.d.ts +76 -0
- package/dist/global/index.t.d.ts.map +1 -0
- package/dist/global/index.t.js +2 -0
- package/dist/global/index.t.js.map +1 -0
- package/dist/global/useRerender.t.d.ts +26 -0
- package/dist/global/useRerender.t.d.ts.map +1 -0
- package/dist/global/useRerender.t.js +2 -0
- package/dist/global/useRerender.t.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +25 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +47 -0
- package/dist/logger.js.map +1 -0
- package/dist/useWhyRerender.d.ts +95 -0
- package/dist/useWhyRerender.d.ts.map +1 -0
- package/dist/useWhyRerender.js +126 -0
- package/dist/useWhyRerender.js.map +1 -0
- package/dist/utils/Config.d.ts +379 -0
- package/dist/utils/Config.d.ts.map +1 -0
- package/dist/utils/Config.js +401 -0
- package/dist/utils/Config.js.map +1 -0
- package/dist/utils/utils.d.ts +28 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +38 -0
- package/dist/utils/utils.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [YEAR] [COPYRIGHT HOLDER]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Why Render
|
|
2
|
+
|
|
3
|
+
React DevTools shows that something re-rendered. Why Render shows you why.
|
|
4
|
+
|
|
5
|
+
Add `useWhyRerender` to any component and get a console diff of exactly which props changed since the last render, so you can immediately tell whether a re-render was caused by a real prop change — or something else entirely.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Install](#install)
|
|
10
|
+
- [Setup](#setup)
|
|
11
|
+
- [Wiring up global setup](#wiring-up-global-setup)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [Options](#options)
|
|
14
|
+
- [Examples](#examples)
|
|
15
|
+
- [1. Basic component usage](#1-basic-component-usage)
|
|
16
|
+
- [2. A component whose props change](#2-a-component-whose-props-change)
|
|
17
|
+
- [3. A component that re-renders without its props changing](#3-a-component-that-re-renders-without-its-props-changing)
|
|
18
|
+
- [4. Inspecting previous and next values with `verbose`](#4-inspecting-previous-and-next-values-with-verbose)
|
|
19
|
+
- [5. Temporarily disabling tracking for one component](#5-temporarily-disabling-tracking-for-one-component)
|
|
20
|
+
- [6. A realistic parent/child example](#6-a-realistic-parentchild-example)
|
|
21
|
+
- [Expected Output](#expected-output)
|
|
22
|
+
- [Troubleshooting](#troubleshooting)
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install why-render-react
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
Why Render is off by default. Call `setup()` once, before your app renders, to turn it on:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { setup } from "why-render-react"
|
|
36
|
+
|
|
37
|
+
setup(true)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`setup()` also accepts strings, so you can wire it to an environment variable:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
setup(process.env.USE_WHY_RENDER) // enabled only if the value is "true" (case-insensitive)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Pass `false` (or leave it unset) to keep Why Render off:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
setup(false)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This is a one-time, app-wide switch — call it once at your entry point, not inside individual components.
|
|
53
|
+
|
|
54
|
+
> **Important:** if any component calls `useWhyRerender` before `setup()` has run, it will throw an error. Always call `setup()` first.
|
|
55
|
+
|
|
56
|
+
### Wiring up global setup
|
|
57
|
+
|
|
58
|
+
`setup()` only needs to run once for your whole app — typically in your entry file, before rendering:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// main.tsx
|
|
62
|
+
import { setup } from "why-render-react"
|
|
63
|
+
import { createRoot } from "react-dom/client"
|
|
64
|
+
import App from "./App"
|
|
65
|
+
|
|
66
|
+
setup(process.env.NODE_ENV !== "production")
|
|
67
|
+
|
|
68
|
+
createRoot(document.getElementById("root")!).render(<App />)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every component that calls `useWhyRerender` anywhere in the tree will start (or stop) logging based on that single call. *(full file: [`examples/00-setup.tsx`](./examples/00-setup.tsx))*
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
Add `useWhyRerender` inside any component you want to debug, passing it a name and that component's current props:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useWhyRerender } from "why-render-react"
|
|
79
|
+
|
|
80
|
+
function CarCard(props: CarCardProps) {
|
|
81
|
+
useWhyRerender({ name: "CarCard", props })
|
|
82
|
+
|
|
83
|
+
return <div>{props.color}</div>
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
It's a React hook, so it follows the same rules as other hooks — call it unconditionally, at the top level of your component. Every time `CarCard` renders, Why Render logs what changed (or didn't) since the last render.
|
|
88
|
+
|
|
89
|
+
## Options
|
|
90
|
+
|
|
91
|
+
`useWhyRerender` takes a single options object:
|
|
92
|
+
|
|
93
|
+
| Option | Type | Required | Default | What it does |
|
|
94
|
+
| ------------ | ----------- | -------- | --------- | --------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `name` | `string` | Yes | — | The label shown for this component in the console output. |
|
|
96
|
+
| `props` | `object` | Yes | — | The component's current props to track. |
|
|
97
|
+
| `verbose` | `boolean` | No | `false` | Also logs each changed prop's previous and next value. |
|
|
98
|
+
| `isActive` | `boolean` | No | `true` | Set to`false` to turn off logging for just this component, without affecting anything else. |
|
|
99
|
+
|
|
100
|
+
> Note: the field is `isActive`. If your editor or docs show `isActivate` anywhere, that's an internal naming detail — the option you pass is `isActive`.
|
|
101
|
+
|
|
102
|
+
## Examples
|
|
103
|
+
|
|
104
|
+
### 1. Basic component usage
|
|
105
|
+
|
|
106
|
+
*(full file: [`examples/01-basic-usage.tsx`](./examples/01-basic-usage.tsx))*
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
function CarCard(props: CarCardProps) {
|
|
110
|
+
useWhyRerender({ name: "CarCard", props })
|
|
111
|
+
|
|
112
|
+
return <div>{props.color}</div>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 2. A component whose props change
|
|
117
|
+
|
|
118
|
+
*(full file: [`examples/02-props-change.tsx`](./examples/02-props-change.tsx))*
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
function CarCard({ color }: { color: string }) {
|
|
122
|
+
useWhyRerender({ name: "CarCard", props: { color } })
|
|
123
|
+
return <div>{color}</div>
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Garage() {
|
|
127
|
+
const [color, setColor] = useState("blue")
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<button onClick={() => setColor("red")}>Repaint</button>
|
|
132
|
+
<CarCard color={color} />
|
|
133
|
+
</>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Clicking "Repaint" changes `color` and re-renders `CarCard`. The console shows `color` as the prop that changed — confirming the re-render was caused by that prop.
|
|
139
|
+
|
|
140
|
+
### 3. A component that re-renders without its props changing
|
|
141
|
+
|
|
142
|
+
Components can re-render for reasons that have nothing to do with their own props — most commonly, because their parent re-rendered. This is exactly the case Why Render helps you catch:
|
|
143
|
+
|
|
144
|
+
*(full file: [`examples/03-rerender-without-prop-change.tsx`](./examples/03-rerender-without-prop-change.tsx))*
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
function CarCard({ color }: { color: string }) {
|
|
148
|
+
useWhyRerender({ name: "CarCard", props: { color } })
|
|
149
|
+
return <div>{color}</div>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function Garage() {
|
|
153
|
+
const [, forceTick] = useState(0)
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<>
|
|
157
|
+
{/* Re-renders Garage (and CarCard) every second, but color never changes */}
|
|
158
|
+
<button onClick={() => forceTick((t) => t + 1)}>Refresh</button>
|
|
159
|
+
<CarCard color="blue" />
|
|
160
|
+
</>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Clicking "Refresh" re-renders `Garage`, which re-renders `CarCard` even though `color` is still `"blue"`. Why Render logs that no props changed on that render — telling you to look at the parent, state, or context instead of the props.
|
|
166
|
+
|
|
167
|
+
### 4. Inspecting previous and next values with `verbose`
|
|
168
|
+
|
|
169
|
+
*(full file: [`examples/04-verbose.tsx`](./examples/04-verbose.tsx))*
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function CarCard({ color }: { color: string }) {
|
|
173
|
+
useWhyRerender({ name: "CarCard", props: { color }, verbose: true })
|
|
174
|
+
return <div>{color}</div>
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
With `verbose: true`, each changed prop's previous and next value is included in the output, so you don't have to guess what it changed *from* and *to*.
|
|
179
|
+
|
|
180
|
+
### 5. Temporarily disabling tracking for one component
|
|
181
|
+
|
|
182
|
+
*(full file: [`examples/05-disable-instance.tsx`](./examples/05-disable-instance.tsx))*
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
function NoisyWidget(props: NoisyWidgetProps) {
|
|
186
|
+
useWhyRerender({ name: "NoisyWidget", props, isActive: false })
|
|
187
|
+
return (/* ... */)
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use this when one component re-renders constantly (e.g. on every keystroke) and its logging is drowning out everything else. Setting `isActive: false` only silences `NoisyWidget` — `setup()` stays enabled, and every other component's `useWhyRerender` call keeps logging normally:
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
function Dashboard(props: DashboardProps) {
|
|
195
|
+
useWhyRerender({ name: "Dashboard", props }) // still logs
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<>
|
|
199
|
+
<Sidebar {...sidebarProps} /> {/* still logs */}
|
|
200
|
+
<NoisyWidget {...widgetProps} /> {/* silenced */}
|
|
201
|
+
</>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 6. A realistic parent/child example
|
|
207
|
+
|
|
208
|
+
*(full file: [`examples/06-parent-child.tsx`](./examples/06-parent-child.tsx))*
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
function CarCard({ id, color, onSelect }: CarCardProps) {
|
|
212
|
+
useWhyRerender({ name: "CarCard", props: { id, color, onSelect }, verbose: true })
|
|
213
|
+
|
|
214
|
+
return <div onClick={() => onSelect(id)}>{color}</div>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function CarList({ cars }: { cars: Car[] }) {
|
|
218
|
+
const [selectedId, setSelectedId] = useState<number | null>(null)
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<>
|
|
222
|
+
{cars.map((car) => (
|
|
223
|
+
<CarCard
|
|
224
|
+
key={car.id}
|
|
225
|
+
id={car.id}
|
|
226
|
+
color={car.color}
|
|
227
|
+
// A new function is created on every CarList render, so this prop
|
|
228
|
+
// "changes" on every render even though the click behavior is the same.
|
|
229
|
+
onSelect={(id) => setSelectedId(id)}
|
|
230
|
+
/>
|
|
231
|
+
))}
|
|
232
|
+
</>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Every time `CarList` re-renders (e.g. when `selectedId` changes), each `CarCard` gets a brand-new `onSelect` function. Why Render flags `onSelect` as changed on every render — a common, easy-to-miss cause of unnecessary re-renders that's hard to spot just by eyeballing the code.
|
|
238
|
+
|
|
239
|
+
## Expected Output
|
|
240
|
+
|
|
241
|
+
**First render — nothing to compare yet:**
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
[why-rerender] <CarCard> render #1 → initial mount
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**A later render where a prop actually changed** (this line expands to show details):
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
▶ [why-rerender] <CarCard> render #3 → 1 prop changed
|
|
251
|
+
CHANGED color
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
With `verbose: true`, the same line also includes the previous and next values:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
▶ [why-rerender] <CarCard> render #3 → 1 prop changed
|
|
258
|
+
CHANGED color { prev: "blue", next: "red" }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**A render where no props changed** (see [Example 3](#3-a-component-that-re-renders-without-its-props-changing)):
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
[why-rerender] <CarCard> render #4 → no prop changes — check state, context, or parent re-render
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Troubleshooting
|
|
268
|
+
|
|
269
|
+
**Error: "WhyRenderConfig has not been configured"** — `setup()` hasn't run yet. Call it once, before your app renders (see [Setup](#setup)).
|
|
270
|
+
|
|
271
|
+
**Nothing is logging, and no error was thrown** — check:
|
|
272
|
+
|
|
273
|
+
1. `setup()` was actually called with a value that resolves to enabled — `setup(false)`, a missing environment variable, or a typo like `"tru"` will silently disable it.
|
|
274
|
+
2. That component's call doesn't have `isActive: false` set (see [Example 5](#5-temporarily-disabling-tracking-for-one-component)).
|
|
275
|
+
|
|
276
|
+
**One component is too noisy to debug around** — set `isActive: false` on that component instead of disabling `setup()` for the whole app.
|
|
277
|
+
|
|
278
|
+
**A prop keeps showing as "changed" even though it looks the same** — this usually means a new object, array, or function is being created for that prop on every render (see [Example 6](#6-a-realistic-parentchild-example)). Since these are different values in JavaScript even when their contents look identical, Why Render correctly reports them as changed.
|
|
279
|
+
|
|
280
|
+
# License
|
|
281
|
+
|
|
282
|
+
### MIT License
|
|
283
|
+
|
|
284
|
+
Copyright (c) why-render contributors
|
|
285
|
+
|
|
286
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
287
|
+
|
|
288
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
289
|
+
|
|
290
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DIFFERENT_PROPS, PropChange } from "./global/index.t.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compares a component's previous and current props and returns the
|
|
4
|
+
* list of props that were added, removed, or changed.
|
|
5
|
+
*
|
|
6
|
+
* Values are compared with `Object.is`, so:
|
|
7
|
+
* - Primitives are compared by value (`NaN` is treated as equal to
|
|
8
|
+
* `NaN`; `+0` and `-0` are treated as different).
|
|
9
|
+
* - Objects, arrays, and functions are compared by reference — a new
|
|
10
|
+
* object/array/function on every render (e.g. an inline `() => {}` or
|
|
11
|
+
* `{ ...props }`) is reported as `"changed"` even if its contents are
|
|
12
|
+
* identical.
|
|
13
|
+
*
|
|
14
|
+
* @param prev - Props from the previous render, or `null` on the first
|
|
15
|
+
* render.
|
|
16
|
+
* @param next - Props from the current render.
|
|
17
|
+
*
|
|
18
|
+
* @returns
|
|
19
|
+
* An empty array when `prev` is `null` (initial mount) or when no props
|
|
20
|
+
* differ. Otherwise, one `PropChange` entry per prop that was added,
|
|
21
|
+
* removed, or changed. Keys are iterated via a `Set` built from
|
|
22
|
+
* `Object.keys(prev)` followed by `Object.keys(next)`, so ordering
|
|
23
|
+
* follows first-seen order across the two objects rather than any
|
|
24
|
+
* guaranteed sort.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* diffProps({ prev: { id: 1 }, next: { id: 2 } })
|
|
29
|
+
* // => [{ prop: "id", prev: 1, next: 2, type: "changed" }]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare const diffProps: ({ prev, next }: DIFFERENT_PROPS) => PropChange[];
|
|
33
|
+
export default diffProps;
|
|
34
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,SAAS,GAAI,gBAAa,eAAe,KAAE,UAAU,EAuB1D,CAAA;AAED,eAAe,SAAS,CAAA"}
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares a component's previous and current props and returns the
|
|
3
|
+
* list of props that were added, removed, or changed.
|
|
4
|
+
*
|
|
5
|
+
* Values are compared with `Object.is`, so:
|
|
6
|
+
* - Primitives are compared by value (`NaN` is treated as equal to
|
|
7
|
+
* `NaN`; `+0` and `-0` are treated as different).
|
|
8
|
+
* - Objects, arrays, and functions are compared by reference — a new
|
|
9
|
+
* object/array/function on every render (e.g. an inline `() => {}` or
|
|
10
|
+
* `{ ...props }`) is reported as `"changed"` even if its contents are
|
|
11
|
+
* identical.
|
|
12
|
+
*
|
|
13
|
+
* @param prev - Props from the previous render, or `null` on the first
|
|
14
|
+
* render.
|
|
15
|
+
* @param next - Props from the current render.
|
|
16
|
+
*
|
|
17
|
+
* @returns
|
|
18
|
+
* An empty array when `prev` is `null` (initial mount) or when no props
|
|
19
|
+
* differ. Otherwise, one `PropChange` entry per prop that was added,
|
|
20
|
+
* removed, or changed. Keys are iterated via a `Set` built from
|
|
21
|
+
* `Object.keys(prev)` followed by `Object.keys(next)`, so ordering
|
|
22
|
+
* follows first-seen order across the two objects rather than any
|
|
23
|
+
* guaranteed sort.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* diffProps({ prev: { id: 1 }, next: { id: 2 } })
|
|
28
|
+
* // => [{ prop: "id", prev: 1, next: 2, type: "changed" }]
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
const diffProps = ({ prev, next }) => {
|
|
32
|
+
if (!prev)
|
|
33
|
+
return [];
|
|
34
|
+
const changes = [];
|
|
35
|
+
const keys = new Set([
|
|
36
|
+
...Object.keys(prev),
|
|
37
|
+
...Object.keys(next),
|
|
38
|
+
]);
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const inPrev = key in prev;
|
|
41
|
+
const inNext = key in next;
|
|
42
|
+
if (!inPrev) {
|
|
43
|
+
changes.push({ prop: key, prev: undefined, next: next[key], type: "added" });
|
|
44
|
+
}
|
|
45
|
+
else if (!inNext) {
|
|
46
|
+
changes.push({ prop: key, prev: prev[key], next: undefined, type: "removed" });
|
|
47
|
+
}
|
|
48
|
+
else if (!Object.is(prev[key], next[key])) {
|
|
49
|
+
changes.push({ prop: key, prev: prev[key], next: next[key], type: "changed" });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return changes;
|
|
53
|
+
};
|
|
54
|
+
export default diffProps;
|
|
55
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,SAAS,GAAG,CAAC,EAAC,IAAI,EAAE,IAAI,EAAiB,EAAe,EAAE;IAC5D,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,MAAM,OAAO,GAAiB,EAAE,CAAA;IAEhC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC;QACjB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACpB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;KACvB,CAAC,CAAA;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,CAAA;QAC1B,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,CAAA;QAE1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAC,SAAS,EAAE,IAAI,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,EAAC,CAAC,CAAA;QAC5E,CAAC;aAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAC,SAAS,EAAE,IAAI,EAAE,SAAS,EAAC,CAAC,CAAA;QAC9E,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,EAAC,CAAC,CAAA;QAC9E,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAA;AAClB,CAAC,CAAA;AAED,eAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminates the kind of change detected for a single prop between
|
|
3
|
+
* two renders.
|
|
4
|
+
*
|
|
5
|
+
* - `"changed"` — the prop exists on both the previous and next render,
|
|
6
|
+
* but its value differs (compared with `Object.is`).
|
|
7
|
+
* - `"added"` — the prop exists on the next render but did not exist on
|
|
8
|
+
* the previous render.
|
|
9
|
+
* - `"removed"` — the prop existed on the previous render but no longer
|
|
10
|
+
* exists on the next render.
|
|
11
|
+
*/
|
|
12
|
+
export type PropChangeType = 'changed' | 'added' | 'removed';
|
|
13
|
+
/**
|
|
14
|
+
* Shape of a component's props object as seen by Why Render.
|
|
15
|
+
*
|
|
16
|
+
* This is intentionally a loose `Record<string, unknown>` so that
|
|
17
|
+
* `useWhyRerender` can accept any component's props without requiring
|
|
18
|
+
* callers to type them explicitly.
|
|
19
|
+
*/
|
|
20
|
+
export type META_TYPE = Record<string, unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Describes a single prop that differs between the previous and the
|
|
23
|
+
* current render, as produced by `diffProps`.
|
|
24
|
+
*/
|
|
25
|
+
export interface PropChange {
|
|
26
|
+
/** The prop's key/name. */
|
|
27
|
+
prop: string;
|
|
28
|
+
/** The prop's value on the previous render, or `undefined` if it was added. */
|
|
29
|
+
prev: unknown;
|
|
30
|
+
/** The prop's value on the current render, or `undefined` if it was removed. */
|
|
31
|
+
next: unknown;
|
|
32
|
+
/** What kind of change this is. */
|
|
33
|
+
type: PropChangeType;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Input to `diffProps`: the previous and current props objects to
|
|
37
|
+
* compare.
|
|
38
|
+
*/
|
|
39
|
+
export interface DIFFERENT_PROPS {
|
|
40
|
+
/**
|
|
41
|
+
* Props from the previous render, or `null` on the very first render
|
|
42
|
+
* (in which case `diffProps` returns an empty array).
|
|
43
|
+
*/
|
|
44
|
+
prev: META_TYPE | null;
|
|
45
|
+
/** Props from the current render. */
|
|
46
|
+
next: META_TYPE;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Identifies a component instance and how many times it has rendered,
|
|
50
|
+
* used to build the console log header.
|
|
51
|
+
*/
|
|
52
|
+
export interface LabelOptions {
|
|
53
|
+
/** Human-readable component name, as passed to `useWhyRerender`. */
|
|
54
|
+
name: string;
|
|
55
|
+
/** 1-based render count for this component instance. */
|
|
56
|
+
count: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options passed to the internal `log` function to print a single
|
|
60
|
+
* render's console output.
|
|
61
|
+
*/
|
|
62
|
+
export interface LogOptions extends LabelOptions {
|
|
63
|
+
/**
|
|
64
|
+
* The list of prop changes to print, or one of two sentinel strings:
|
|
65
|
+
* `"initial"` for the first render, or `"no-prop-changes"` for a
|
|
66
|
+
* render the hook determined had no prop changes.
|
|
67
|
+
*/
|
|
68
|
+
changes: PropChange[] | 'initial' | 'no-prop-changes';
|
|
69
|
+
/**
|
|
70
|
+
* When `true`, each changed prop is logged together with its
|
|
71
|
+
* previous and next values. When `false`, only the prop name and
|
|
72
|
+
* change type are logged.
|
|
73
|
+
*/
|
|
74
|
+
verbose: boolean;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=index.t.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.t.d.ts","sourceRoot":"","sources":["../../src/global/index.t.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAA;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE/C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACvB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,EAAE,OAAO,CAAC;IACd,gFAAgF;IAChF,IAAI,EAAE,OAAO,CAAC;IACd,mCAAmC;IACnC,IAAI,EAAE,cAAc,CAAC;CACxB;AAGD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B;;;OAGG;IACH,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,qCAAqC;IACrC,IAAI,EAAE,SAAS,CAAC;CACnB;AAGD;;;EAGE;AACF,MAAM,WAAW,YAAY;IACzB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACjB;AAGD;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC5C;;;;OAIG;IACH,OAAO,EAAE,UAAU,EAAE,GAAG,SAAS,GAAG,iBAAiB,CAAC;IACtD;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAA;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.t.js","sourceRoot":"","sources":["../../src/global/index.t.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { META_TYPE } from "./index.t.js";
|
|
2
|
+
/**
|
|
3
|
+
* Arguments accepted by `useWhyRerender`.
|
|
4
|
+
*/
|
|
5
|
+
export interface Rerender {
|
|
6
|
+
/** Human-readable name for the component, used in console output. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The component's current props to compare against the previous render. */
|
|
9
|
+
props: META_TYPE;
|
|
10
|
+
/**
|
|
11
|
+
* When `true`, logs the previous and next value for each changed
|
|
12
|
+
* prop. Defaults to `false` when omitted.
|
|
13
|
+
*/
|
|
14
|
+
verbose?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
*Controls whether Why Render should track this component.
|
|
17
|
+
* Set to `false` to temporarily disable logging for this component.
|
|
18
|
+
* This does not disable Why Render globally.
|
|
19
|
+
* @default true
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* isActivate: false * ```
|
|
23
|
+
* */
|
|
24
|
+
isActive?: boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=useRerender.t.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRerender.t.d.ts","sourceRoot":"","sources":["../../src/global/useRerender.t.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAG7C;;EAEE;AACF,MAAM,WAAW,QAAQ;IACrB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,EAAC,SAAS,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;QAQI;IACJ,QAAQ,CAAC,EAAC,OAAO,CAAA;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRerender.t.js","sourceRoot":"","sources":["../../src/global/useRerender.t.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EACH,cAAc,EACd,KAAK,EACL,KAAK,QAAQ,EAChB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EACH,cAAc,EACd,KAAK,EAER,CAAA"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LogOptions } from "./global/index.t.js";
|
|
2
|
+
/**
|
|
3
|
+
* Prints a single render's diagnostic output to the console.
|
|
4
|
+
*
|
|
5
|
+
* Behavior depends on `changes`:
|
|
6
|
+
* - `"initial"` — logs a single `console.log` line noting the initial
|
|
7
|
+
* mount.
|
|
8
|
+
* - `"no-prop-changes"` — logs a single `console.log` line suggesting
|
|
9
|
+
* the re-render was caused by state, context, or a parent re-render
|
|
10
|
+
* rather than a prop change.
|
|
11
|
+
* - An array of `PropChange` — opens a collapsed console group
|
|
12
|
+
* (`console.groupCollapsed` / `console.groupEnd`) with one color-coded
|
|
13
|
+
* `console.log` line per entry, labeled `CHANGED`, `ADDED`, or
|
|
14
|
+
* `REMOVED`. When `verbose` is `true`, each line also includes an
|
|
15
|
+
* object with the prop's previous and next values.
|
|
16
|
+
*
|
|
17
|
+
* This function only produces console output; it has no return value
|
|
18
|
+
* and throws no errors of its own.
|
|
19
|
+
*
|
|
20
|
+
* @param LogOptions - The component name, render count, changes to report,
|
|
21
|
+
* and verbosity flag.
|
|
22
|
+
*/
|
|
23
|
+
declare const log: ({ name, count, changes, verbose }: LogOptions) => void;
|
|
24
|
+
export default log;
|
|
25
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAMtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,QAAA,MAAM,GAAG,GAAI,mCAAiC,UAAU,KAAE,IA8BzD,CAAA;AAED,eAAe,GAAG,CAAA"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PAD_SIZE, setBodyText, setHeader, LABEL_STYLE } from "./utils/utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Prints a single render's diagnostic output to the console.
|
|
4
|
+
*
|
|
5
|
+
* Behavior depends on `changes`:
|
|
6
|
+
* - `"initial"` — logs a single `console.log` line noting the initial
|
|
7
|
+
* mount.
|
|
8
|
+
* - `"no-prop-changes"` — logs a single `console.log` line suggesting
|
|
9
|
+
* the re-render was caused by state, context, or a parent re-render
|
|
10
|
+
* rather than a prop change.
|
|
11
|
+
* - An array of `PropChange` — opens a collapsed console group
|
|
12
|
+
* (`console.groupCollapsed` / `console.groupEnd`) with one color-coded
|
|
13
|
+
* `console.log` line per entry, labeled `CHANGED`, `ADDED`, or
|
|
14
|
+
* `REMOVED`. When `verbose` is `true`, each line also includes an
|
|
15
|
+
* object with the prop's previous and next values.
|
|
16
|
+
*
|
|
17
|
+
* This function only produces console output; it has no return value
|
|
18
|
+
* and throws no errors of its own.
|
|
19
|
+
*
|
|
20
|
+
* @param LogOptions - The component name, render count, changes to report,
|
|
21
|
+
* and verbosity flag.
|
|
22
|
+
*/
|
|
23
|
+
const log = ({ name, count, changes, verbose }) => {
|
|
24
|
+
const header = setHeader({ name, count });
|
|
25
|
+
if (changes === "initial") {
|
|
26
|
+
console.log(header, LABEL_STYLE, '→ initial mount');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (changes === "no-prop-changes") {
|
|
30
|
+
console.log(header, LABEL_STYLE, '→ no prop changes — check state, context, or parent re-render');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.groupCollapsed(header, LABEL_STYLE, `→ ${changes.length} prop${changes.length > 1 ? 's' : ''} changed`);
|
|
34
|
+
for (const c of changes) {
|
|
35
|
+
const label = c.type.toUpperCase().padEnd(PAD_SIZE);
|
|
36
|
+
const typeStyle = setBodyText(c.type);
|
|
37
|
+
if (verbose) {
|
|
38
|
+
console.log(`%c ${label} %c${c.prop}`, typeStyle, '', { prev: c.prev, next: c.next });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(`%c ${label} %c${c.prop}`, typeStyle, '');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.groupEnd();
|
|
45
|
+
};
|
|
46
|
+
export default log;
|
|
47
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAKjF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,GAAG,GAAG,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAa,EAAO,EAAE;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACzC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;QACnD,OAAO;IACX,CAAC;IAED,IAAI,OAAO,KAAK,iBAAiB,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,+DAA+D,CAAC,CAAA;QACjG,OAAM;IACV,CAAC;IAED,OAAO,CAAC,cAAc,CAClB,MAAM,EACN,WAAW,EACX,KAAK,OAAO,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,CACrE,CAAA;IAED,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACnD,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAErC,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QACzF,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAA;QACzD,CAAC;IACL,CAAC;IACD,OAAO,CAAC,QAAQ,EAAE,CAAA;AAEtB,CAAC,CAAA;AAED,eAAe,GAAG,CAAA"}
|