Skip to content

Commit

Permalink
fix flicker with observedValues buffer (#697)
Browse files Browse the repository at this point in the history
We do this by constructing a buffer of observed input values. Then, each time the server renders, we check to see if the next rendered value matches the first value in the buffer. If it does then we continue to display the last value in the buffer and shift the buffer array. If it doesn't then we clear the buffer array and display that rendered value.
  • Loading branch information
rmorshea authored Mar 3, 2022
1 parent a029189 commit 857c789
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 4 deletions.
21 changes: 20 additions & 1 deletion src/client/packages/idom-client-react/src/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function UserInputElement({ model }) {
// 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;
let value = props.value;
delete props.value;

// Instead of controlling the value, we set it in an effect.
Expand All @@ -91,6 +91,25 @@ function UserInputElement({ model }) {
}
}, [ref.current, value]);

// Track a buffer of observed values in order to avoid flicker
const observedValues = React.useState([])[0];
if (observedValues) {
if (value === observedValues[0]) {
observedValues.shift();
value = observedValues[observedValues.length - 1];
} else {
observedValues.length = 0;
}
}

const givenOnChange = props.onChange;
if (typeof givenOnChange === "function") {
props.onChange = (event) => {
observedValues.push(event.target.value);
givenOnChange(event);
};
}

// 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.
Expand Down
39 changes: 36 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import asyncio
import time
from pathlib import Path

import idom
from idom.testing import ServerMountPoint
from tests.driver_utils import send_keys


JS_DIR = Path(__file__).parent / "js"
Expand Down Expand Up @@ -71,14 +74,44 @@ def ButtonWithChangingColor():

button = driver.find_element("id", "my-button")

assert get_style(button)["background-color"] == "red"
assert _get_style(button)["background-color"] == "red"

for color in ["blue", "red"] * 2:
button.click()
driver_wait.until(lambda _: get_style(button)["background-color"] == color)
driver_wait.until(lambda _: _get_style(button)["background-color"] == color)


def get_style(element):
def _get_style(element):
items = element.get_attribute("style").split(";")
pairs = [item.split(":", 1) for item in map(str.strip, items) if item]
return {key.strip(): value.strip() for key, value in pairs}


def test_slow_server_response_on_input_change(display, driver, driver_wait):
"""A delay server-side could cause input values to be overwritten.
For more info see: https://github.com/idom-team/idom/issues/684
"""

delay = 0.2

@idom.component
def SomeComponent():
value, set_value = idom.hooks.use_state("")

async def handle_change(event):
await asyncio.sleep(delay)
set_value(event["target"]["value"])

return idom.html.input({"onChange": handle_change, "id": "test-input"})

display(SomeComponent)

inp = driver.find_element("id", "test-input")

text = "hello"
send_keys(inp, text)

time.sleep(delay * len(text) * 1.1)

driver_wait.until(lambda _: inp.get_attribute("value") == "hello")

0 comments on commit 857c789

Please sign in to comment.