Skip to content

Commit 0fa8435

Browse files
committed
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.
1 parent c9536f6 commit 0fa8435

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
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.

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)