Skip to content

Commit 16df269

Browse files
committed
correcly apply client-side JSON patch
1 parent 57810bb commit 16df269

File tree

2 files changed

+139
-17
lines changed

2 files changed

+139
-17
lines changed

src/client/packages/idom-client-react/src/utils.js

+36-17
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,50 @@ import React from "react";
22
import jsonpatch from "fast-json-patch";
33

44
export function useJsonPatchCallback(initial) {
5-
const model = React.useRef(initial);
5+
const doc = React.useRef(initial);
66
const forceUpdate = useForceUpdate();
77

88
const applyPatch = React.useCallback(
9-
(pathPrefix, patch) => {
10-
if (pathPrefix) {
11-
patch = patch.map((op) =>
12-
Object.assign({}, op, { path: pathPrefix + op.path })
13-
);
9+
(path, patch) => {
10+
if (!path) {
11+
// We CANNOT mutate the part of the document because React checks some
12+
// attributes of the model (e.g. model.attributes.style is checked for
13+
// identity).
14+
doc.current = applyNonMutativePatch(doc, patch, false, false, true);
15+
} else {
16+
// We CAN mutate the document here though because we know that nothing above
17+
// The patch `path` is changing. Thus, maintaining the identity for that section
18+
// of the model is accurate.
19+
applyMutativePatch(doc.current, [
20+
{
21+
op: "replace",
22+
path: path,
23+
// We CANNOT mutate the part of the document where the actual patch is being
24+
// applied. Instead we create a copy because React checks some attributes of
25+
// the model (e.g. model.attributes.style is checked for identity). The part
26+
// of the document above the `path` can be mutated though because we know it
27+
// has not changed.
28+
value: applyNonMutativePatch(
29+
jsonpatch.getValueByPointer(doc.current, path),
30+
patch
31+
),
32+
},
33+
]);
1434
}
15-
// Always return a newDocument because React checks some attributes of the model
16-
// (e.g. model.attributes.style is checked for identity)
17-
model.current = jsonpatch.applyPatch(
18-
model.current,
19-
patch,
20-
false,
21-
false,
22-
true
23-
).newDocument;
2435
forceUpdate();
2536
},
26-
[model]
37+
[doc]
2738
);
2839

29-
return [model.current, applyPatch];
40+
return [doc.current, applyPatch];
41+
}
42+
43+
function applyNonMutativePatch(doc, patch) {
44+
return jsonpatch.applyPatch(doc, patch, false, false, true).newDocument;
45+
}
46+
47+
function applyMutativePatch(doc, patch) {
48+
jsonpatch.applyPatch(doc, patch, false, true, true).newDocument;
3049
}
3150

3251
function useForceUpdate() {

temp.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import random
2+
3+
import idom
4+
5+
6+
@idom.component
7+
def RangeWords(text):
8+
highlight_range, set_highlight_range = idom.hooks.use_state([])
9+
10+
def update_range(index):
11+
if highlight_range == []:
12+
set_highlight_range([index])
13+
elif highlight_range == [index]:
14+
set_highlight_range([])
15+
elif index in [highlight_range[-1], highlight_range[0]]:
16+
set_highlight_range([index])
17+
elif index < highlight_range[0]:
18+
set_highlight_range(list(range(index, highlight_range[0] + 1)))
19+
else:
20+
set_highlight_range(list(range(highlight_range[0], index + 1)))
21+
22+
divs = [
23+
idom.html.div(
24+
{
25+
"onClick": lambda e, i=i: update_range(i),
26+
"style": {
27+
"backgroundColor": "aquamarine"
28+
if i in highlight_range
29+
else "white",
30+
"display": "inline",
31+
"padding": "5px",
32+
},
33+
},
34+
word,
35+
key=str(random.random()),
36+
)
37+
for i, word in enumerate(text.split())
38+
]
39+
40+
return idom.html.div(
41+
{
42+
"style": {
43+
"width": "95%",
44+
"display": "flex",
45+
"flex-wrap": "wrap",
46+
"margin": "5px",
47+
}
48+
},
49+
divs,
50+
)
51+
52+
53+
@idom.component
54+
def WorkSentDiv(tfname):
55+
56+
sid, set_sid = idom.hooks.use_state(0)
57+
58+
works = {
59+
"bubs": [
60+
"it was the best of times, it was the blurst of times.",
61+
"they will break upon this fortress like water on rock.",
62+
"ah those heady days of yore.",
63+
"forge ahead, no matter the cost.",
64+
"bring me the head of alfredo garcia.",
65+
"there were monsters aboard that ship, and truly we were them.",
66+
],
67+
"zugzug": [
68+
"나도너도나도 웰웰웰 아하아하.",
69+
"섬웨어 오버 더 레인보.",
70+
"유 캔트 듀 댓 온 테레비즌.",
71+
"컴퓨터 프로그램잉.",
72+
"어메이징 테일즈.",
73+
"대츠 오케이!",
74+
"데르즈 노 비지니스 라이크 쇼 비지니스",
75+
],
76+
}
77+
78+
def get_stext(tfname, sid):
79+
if sid < len(works[tfname]):
80+
return works[tfname][sid]
81+
else:
82+
return "no more sentences"
83+
84+
def btn_click(count):
85+
if sid + count < 0:
86+
return
87+
88+
set_sid(sid + count)
89+
90+
return idom.html.div(
91+
idom.html.div(f"sentence id: {sid}"),
92+
idom.html.button({"onClick": lambda e: btn_click(-1)}, "Prev"),
93+
idom.html.button({"onClick": lambda e: btn_click(1)}, "Next"),
94+
RangeWords(get_stext(tfname, sid)),
95+
)
96+
97+
98+
@idom.component
99+
def DualWorkView():
100+
return idom.html.div(WorkSentDiv("bubs"), WorkSentDiv("zugzug"))
101+
102+
103+
idom.run(DualWorkView)

0 commit comments

Comments
 (0)