diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json index 62c2210aa..ceed13e02 100644 --- a/docs/source/_custom_js/package-lock.json +++ b/docs/source/_custom_js/package-lock.json @@ -19,7 +19,7 @@ } }, "../../../src/client/packages/idom-client-react": { - "version": "0.37.0", + "version": "0.37.1-a1", "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==", "license": "MIT", "dependencies": { diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index 9757d5bcc..aae86ad8d 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -16,7 +16,9 @@ scheme for the project adheres to `Semantic Versioning `__. Unreleased ---------- -Nothing yet... +Fixed: + +- Revert :pull:`694` and by making ``value`` uncontrolled client-side - :issue:`684` 0.37.1-a1 @@ -24,7 +26,7 @@ Nothing yet... Fixed: -- ``onChange`` event for inputs missing key strokes :issue:`684` +- ``onChange`` event for inputs missing key strokes - :issue:`684` 0.37.0 diff --git a/src/client/packages/idom-client-react/src/components.js b/src/client/packages/idom-client-react/src/components.js index c44eed085..65e8c8e6c 100644 --- a/src/client/packages/idom-client-react/src/components.js +++ b/src/client/packages/idom-client-react/src/components.js @@ -38,6 +38,8 @@ export function Element({ model }) { } } else if (model.tagName == "script") { return html`<${ScriptElement} model=${model} />`; + } else if (["input", "select", "textarea"].includes(model.tagName)) { + return html`<${UserInputElement} model=${model} />`; } else if (model.importSource) { return html`<${ImportedElement} model=${model} />`; } else { @@ -68,6 +70,45 @@ function StandardElement({ model }) { ); } +// Element with a value attribute controlled by user input +function UserInputElement({ model }) { + const ref = React.useRef(); + const layoutContext = React.useContext(LayoutContext); + + const props = createElementAttributes(model, layoutContext.sendEvent); + + // Because we handle events asynchronously, we must leave the value uncontrolled in + // order to allow all changes 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 next + // causing the content of prior changes to be overwritten by subsequent changes. + const value = props.value; + delete props.value; + + // Instead of controlling the value, we set it in an effect. + React.useEffect(() => { + if (value !== undefined) { + ref.current.value = value; + } + }, [ref.current, value]); + + // Use createElement here to avoid warning about variable numbers of children not + // having keys. Warning about this must now be the responsibility of the server + // providing the models instead of the client rendering them. + return React.createElement( + model.tagName, + { + ...props, + ref: (target) => { + ref.current = target; + }, + }, + ...createElementChildren( + model, + (model) => html`<${Element} key=${model.key} model=${model} />` + ) + ); +} + function ScriptElement({ model }) { const ref = React.useRef(); React.useEffect(() => { diff --git a/src/client/packages/idom-client-react/src/element-utils.js b/src/client/packages/idom-client-react/src/element-utils.js index e9a39458c..cbf13470d 100644 --- a/src/client/packages/idom-client-react/src/element-utils.js +++ b/src/client/packages/idom-client-react/src/element-utils.js @@ -39,8 +39,6 @@ function createEventHandler(eventName, sendEvent, eventSpec) { if (typeof value === "object" && value.nativeEvent) { if (eventSpec["preventDefault"]) { value.preventDefault(); - } else if (eventName === "onChange") { - value.nativeEvent.target.value = value.target.value; } if (eventSpec["stopPropagation"]) { value.stopPropagation();