react-deepwatch 1.7.2 → 1.7.3
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/package.json +2 -2
- package/readme.md +47 -21
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
|
-
# React Deepwatch -
|
|
1
|
+
# React Deepwatch - automatically re-render on data changes
|
|
2
|
+
|
|
3
|
+
This framework eliminates the need to care about any state management or any code that tells a components that it's state has changed, like, i.e. the usually used `set**myVar's**State()` calls.
|
|
4
|
+
Think of just having plain data, (_meaning: props, state, or business object inside your state. Or global data structures where your component uses some small parts of it_).
|
|
5
|
+
If any of this data changes, React Deepwatch **detects those changes automatically and re-renders** the component and also re-runs [load(...) statements](#load) should they depend on that changed data.
|
|
6
|
+
_These inline `load(...)` statements are the second cool trick that this library offers, which comes by as a benefit of this data awareness😎_
|
|
7
|
+
|
|
8
|
+
So how does it work? Can this even work? How is it possible to track all these reads and writes automatically?
|
|
9
|
+
The answer: Javascript offers very powerful (and almost forgotten) language features with allow you to "virtualize" (or trap) every read/write in any data structure.
|
|
10
|
+
On the one hand, there is the mighty [Proxy class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) which can intercept and record all your React-component-function's reads and writes to the data once you hand it that proxy.
|
|
11
|
+
So your component-function will see and use all the data through glasses of proxies.
|
|
12
|
+
But what about if you have existing global data objects that already exist and are passed to your component from the outside? Yes, this data is also tracked and reacts to external changes. This is, on the other hand, achieved by [various javascript tricks](https://github.com/bogeeee/proxy-facades/blob/main/origChangeTracking.ts).
|
|
13
|
+
The proxy stuff and all these "tricks and traps" were abstracted away into [one coherent -and therefore well testable- core layer behind the scenes](https://github.com/bogeeee/proxy-facades).
|
|
14
|
+
The good thing is, that all proxied and instrumented objects **act fully transparent**. You data structures behave exactly the same as a non tracked original would. You can as-usual operate on them by your functions or libraries😊 and React Deepwatch components (watchedComponents) integrate seamlessly in between your existing React component tree and existing data graph.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Performance friendly
|
|
18
|
+
React Deepwatch **watches only those properties that are actually used** in your component function. It doesn't matter how complex and deep the graph (the data) behind your state or props is.
|
|
19
|
+
Only relevant values are watched and will trigger a re-render. And this only for relevant components, meaning not the whole tree under it, as you were used to with classic un-memoized React components.
|
|
20
|
+
|
|
21
|
+
# Quick example to show you the nice features
|
|
22
|
+
There are also other beneficial features that come with this lib.
|
|
23
|
+
Here are all basic features, put together into one example. Hope, you're not overwhelmed with it. Just imagine, what this component does, and how few code is needed to achieve it.
|
|
24
|
+
|
|
25
|
+

|
|
2
26
|
|
|
3
27
|
|
|
4
|
-
**Deeply watches your state-object and props** for changes. **Re-renders** automatically😎 and makes you write much less code😊.
|
|
5
|
-
- **Performance friendly**
|
|
6
|
-
React Deepwatch uses a [proxy-facade](https://github.com/bogeeee/proxy-facades) to **watch only for those properties that are actually used** in your component function. It doesn't matter how complex and deep the graph behind your state or props is.
|
|
7
|
-
- **Can watch your -model- as well**
|
|
8
|
-
If a (used) property in props points to your model, a change there will also trigger a re-render. In fact, you can [watch anything](#watched) ;)
|
|
9
28
|
|
|
10
|
-
# Quick example to show you most features
|
|
11
29
|
````jsx
|
|
12
|
-
// Will reload the fruits and show a 🌀 during load, if you type in the filter box.
|
|
30
|
+
// Will reload the fruits and show a 🌀 during load, if you type something in the filter box.
|
|
13
31
|
const MyComponent = watchedComponent(props => {
|
|
14
32
|
const state = useWatchedState({
|
|
15
33
|
filter: "",
|
|
@@ -20,15 +38,15 @@ const MyComponent = watchedComponent(props => {
|
|
|
20
38
|
{/* A nice bind syntax. No more 'onChange(...)' code */}
|
|
21
39
|
Filter <input type="text" {...bind(state.filter )} />
|
|
22
40
|
|
|
23
|
-
{/* state.filter="" will automatically
|
|
41
|
+
{/* Setting state.filter="" will automatically re-render and re-run the following server fetch, if necessary👍 */}
|
|
24
42
|
<input type="button" value="Clear filter" onClick={() => state.filter = ""} />
|
|
25
43
|
|
|
26
44
|
{/* you can fetch data from **inside** conditional render code or loops😎! No useEffect needed! Knows its dependencies automatically👍 */}
|
|
27
|
-
<div>Here are the fruits, fetched from the Server:<br/><i>{ load(async ()=> await fetchFruitsFromServer(state.filter), {fallback:"loading list 🌀"} )}</i></div><br/>
|
|
45
|
+
<div>Here are the fruits, fetched from the Server:<br/><i>{ load(async ()=> await fetchFruitsFromServer(state.filter), {fallback:"loading the list 🌀"} )}</i></div><br/>
|
|
28
46
|
|
|
29
|
-
{/* The above load(...) code is independent of state.showPrices, react-deepwatch knows that automatically, so clicking here will NOT exec a re- load(...)👍... */}
|
|
47
|
+
{/* Side note: The above load(...) code is **independent** of state.showPrices, react-deepwatch knows that automatically, so clicking here will NOT exec a re- load(...)👍... */}
|
|
30
48
|
Show prices <input type="checkbox" {...bind(state.showPrices)} />
|
|
31
|
-
{/* showing here, that clicking "show prices" will
|
|
49
|
+
{/* showing here, that clicking "show prices" will only do a re-render but not re-do the fetchFruitsFromServer: */}
|
|
32
50
|
{state.showPrices?<div>Free today!</div>:null}
|
|
33
51
|
</div>
|
|
34
52
|
});
|
|
@@ -43,7 +61,8 @@ npm install --save react-deepwatch
|
|
|
43
61
|
````
|
|
44
62
|
|
|
45
63
|
# Usage
|
|
46
|
-
|
|
64
|
+
|
|
65
|
+
## Watched state
|
|
47
66
|
````jsx
|
|
48
67
|
import {watchedComponent, watched, useWatchedState} from "react-deepwatch"
|
|
49
68
|
|
|
@@ -60,8 +79,16 @@ const MyComponent = watchedComponent(props => {
|
|
|
60
79
|
````
|
|
61
80
|
[](https://stackblitz.com/fork/github/bogeeee/react-deepwatch/tree/1.x/examples/no-more-setstate?title=react-deepwatch%20example&file=index.jsx)
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
`state` is now a Proxy. you can modify anything (deep) under it and the component get's re-rendered (when relevant).
|
|
83
|
+
You can also hand parts of the state to child components and let them modify it. No limits here.
|
|
84
|
+
|
|
85
|
+
**Make sure, all sources to your component are watched** if you want them to trigger re-rendering: `props` are already automatically watched; For context, use `watched(useContext(...))`. For any external/global object, wrap it in `watched(myGlobalObject)`.
|
|
86
|
+
So just wrap every external thing in `watch(...)`. Double watching does not hurt;)
|
|
87
|
+
|
|
88
|
+
## Load(...)
|
|
89
|
+
|
|
90
|
+
`load(...)` can async'ly **fetch data anywhere** inside your render code. You don't have to code any useEffect constructs around it anymore.
|
|
91
|
+
Simply load data at exactly the point where you need it. That means, it can even can be inside a conditional block or a loop 👍.
|
|
65
92
|
|
|
66
93
|
````jsx
|
|
67
94
|
import {watchedComponent, load, poll, isLoading, loadFailed, preserve, READS_INSIDE_LOADER_FN} from "react-deepwatch"
|
|
@@ -77,9 +104,8 @@ const MyComponent = watchedComponent(props => {
|
|
|
77
104
|
````
|
|
78
105
|
[](https://stackblitz.com/fork/github/bogeeee/react-deepwatch/tree/1.x/examples/less-loading-code?title=react-deepwatch%20example&file=index.jsx)
|
|
79
106
|
|
|
80
|
-
Note: 👍 load(...) even can be inside a conditional block or a loop 👍.
|
|
81
107
|
The returned Promise will be await'ed and the component will be put into [suspense](https://react.dev/reference/react/Suspense) that long.
|
|
82
|
-
**`load(...)` re-executes `myFetchFromServer`, when a dependent value changes**. For this auto-dependency mechanic to work, **make sure, all sources to your component are watched**: `props` and other `load(...)`'s result are already automatically watched; For state, use `useWatchedState(...)`; For context, use `watched(useContext(...))`.
|
|
108
|
+
**`load(...)` re-executes `myFetchFromServer`, when a dependent value changes**. For this auto-dependency mechanic to work, again, **make sure, all sources to your component are watched**: `props` and other `load(...)`'s result are already automatically watched; For state, use `useWatchedState(...)`; For context, use `watched(useContext(...))`.
|
|
83
109
|
|
|
84
110
|
### Dependencies
|
|
85
111
|
By default, everything prior to the load(...) statement in your code and immediately in your loaderFn is treated as a dependency.
|
|
@@ -111,7 +137,7 @@ const showPrices = state.showPrices;
|
|
|
111
137
|
To show a 🌀loading spinner / placeholder during load, either...
|
|
112
138
|
- **wrap your component in a [`<Suspense fallback={<div>🌀</div>}>...<MyComponent/>...</Suspense>`](https://react.dev/reference/react/Suspense)**. It can be wrapped at any parent level😎. _Or..._
|
|
113
139
|
- **call isLoading()** inside your component, to probe if any or a certain `load(...)`statement is loading. _See jsDoc for usage example. Mind the caveat of not using it for a condition to cut off a load statement._ _and/or..._
|
|
114
|
-
- **specify a fallback** value via `load(..., {fallback:"🌀"})`.
|
|
140
|
+
- **specify a fallback** value via `load(..., {fallback:"🌀"})`. You might return it in the form of your usual data as a quick cheap trick to get it displayed;)
|
|
115
141
|
|
|
116
142
|
### Handle errors
|
|
117
143
|
either...
|
|
@@ -121,7 +147,7 @@ either...
|
|
|
121
147
|
- **call** the **loadFailed()** probing function. This looks more elegant than the above. _See jsDoc for usage example._
|
|
122
148
|
|
|
123
149
|
### Object instance preserving
|
|
124
|
-
Use the `preserve` function on all your fetched data, to smartly ensure non-changing object instances in your app
|
|
150
|
+
Use the `preserve` function on all your fetched data, to smartly ensure non-changing object instances in your app. So to make sure, `newFetchResult` **===** `oldFetchResult`. It does this also for deeper objects. This is the better way, because changed object instances can either cascade to a lot of re-loads or result in bugs because your component is still watching an old instance.
|
|
125
151
|
_Think of it like: The preserve function does for your data, what React does for your component tree: It smartly remembers the instances, if needed with the help of an id or key, and re-applies the re-fetched/re-rendered properties to them, so the object-identity/component-state stays the same._
|
|
126
152
|
👍 `load(...)` does `preserve` its result by default to enforce this paradigm and give you the best, trouble free experience.
|
|
127
153
|
|
|
@@ -161,7 +187,7 @@ _This example will trigger both onChange handlers._
|
|
|
161
187
|
|
|
162
188
|
_Note, that `watched(myState.form) !== myState.form`. It created a new proxy object in a new proxy-facade layer here, just for the purpose of deep-watching everything under it. Keep that in mind, when i.e. comparing objects by instance (I.e. row === selectedRow) if they arrived in different ways. Sometimes you may want to take advantage of it, so that modifications in the originaly layer (in MyParentComponent) won't fire the onChange event / call the postFormToTheSerer function. I.e. for updates that **came** from the server_
|
|
163
189
|
|
|
164
|
-
# ...
|
|
190
|
+
# {...bind}: Less onChange code for <input/> elements
|
|
165
191
|
|
|
166
192
|
Let's make the value binding code a bit easier:
|
|
167
193
|
````jsx
|
|
@@ -194,7 +220,7 @@ Besides `load`, react-deepwatch also supports hosting [retryable-synchronous](ht
|
|
|
194
220
|
There are also other libraries that address proxying the state:
|
|
195
221
|
[valtio](https://github.com/pmndrs/valtio), [react-easy-state](https://github.com/RisingStack/react-easy-state), [wana](https://www.npmjs.com/package/wana),
|
|
196
222
|
|
|
197
|
-
|
|
223
|
+
**⭐⭐React Deepwatch set's its self apart in the following areas:⭐⭐**
|
|
198
224
|
- Deep (not only shallow-) proxying
|
|
199
225
|
- Tracking changes above **and** below the proxy = also on the unproxied object.
|
|
200
226
|
- Full transparent support for `this`, getters/setters (treated as white box), user's methods, Sets, Maps, Arrays _(wana seems to support Sets,Maps, Arrays too)_
|