Skip to content

Commit a029189

Browse files
authored
actually fix #684 (#696)
* actually fix #684 Because we handle events asynchronously, we must leave the value uncontrolled in order to allow all changed committed by the user to be recorded in the order they occur. If we don't, the user may commit multiple changes before we render resulting in prior changes being overwritten by subsequent ones. Instead of controlling the value, we set it in an effect. This feels a bit hacky, but I can't think of a better way to work around this problem. * update changelog
1 parent c9536f6 commit a029189

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

docs/source/_custom_js/package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/source/about/changelog.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ scheme for the project adheres to `Semantic Versioning <https://semver.org/>`__.
1616
Unreleased
1717
----------
1818

19-
Nothing yet...
19+
Fixed:
20+
21+
- Revert :pull:`694` and by making ``value`` uncontrolled client-side - :issue:`684`
2022

2123

2224
0.37.1-a1
2325
---------
2426

2527
Fixed:
2628

27-
- ``onChange`` event for inputs missing key strokes :issue:`684`
29+
- ``onChange`` event for inputs missing key strokes - :issue:`684`
2830

2931

3032
0.37.0

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

+41
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export function Element({ model }) {
3838
}
3939
} else if (model.tagName == "script") {
4040
return html`<${ScriptElement} model=${model} />`;
41+
} else if (["input", "select", "textarea"].includes(model.tagName)) {
42+
return html`<${UserInputElement} model=${model} />`;
4143
} else if (model.importSource) {
4244
return html`<${ImportedElement} model=${model} />`;
4345
} else {
@@ -68,6 +70,45 @@ function StandardElement({ model }) {
6870
);
6971
}
7072

73+
// Element with a value attribute controlled by user input
74+
function UserInputElement({ model }) {
75+
const ref = React.useRef();
76+
const layoutContext = React.useContext(LayoutContext);
77+
78+
const props = createElementAttributes(model, layoutContext.sendEvent);
79+
80+
// Because we handle events asynchronously, we must leave the value uncontrolled in
81+
// order to allow all changes committed by the user to be recorded in the order they
82+
// occur. If we don't the user may commit multiple changes before we render next
83+
// causing the content of prior changes to be overwritten by subsequent changes.
84+
const value = props.value;
85+
delete props.value;
86+
87+
// Instead of controlling the value, we set it in an effect.
88+
React.useEffect(() => {
89+
if (value !== undefined) {
90+
ref.current.value = value;
91+
}
92+
}, [ref.current, value]);
93+
94+
// Use createElement here to avoid warning about variable numbers of children not
95+
// having keys. Warning about this must now be the responsibility of the server
96+
// providing the models instead of the client rendering them.
97+
return React.createElement(
98+
model.tagName,
99+
{
100+
...props,
101+
ref: (target) => {
102+
ref.current = target;
103+
},
104+
},
105+
...createElementChildren(
106+
model,
107+
(model) => html`<${Element} key=${model.key} model=${model} />`
108+
)
109+
);
110+
}
111+
71112
function ScriptElement({ model }) {
72113
const ref = React.useRef();
73114
React.useEffect(() => {

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

-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ function createEventHandler(eventName, sendEvent, eventSpec) {
3939
if (typeof value === "object" && value.nativeEvent) {
4040
if (eventSpec["preventDefault"]) {
4141
value.preventDefault();
42-
} else if (eventName === "onChange") {
43-
value.nativeEvent.target.value = value.target.value;
4442
}
4543
if (eventSpec["stopPropagation"]) {
4644
value.stopPropagation();

0 commit comments

Comments
 (0)