Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to override JavaScript variable

I have a React component which displays a list of "autocomplete" recommendations as the user is typing into the search bar. I am trying to allow for the user to also navigate the list by using the arrow keys and enter key.

enter image description here

When the user presses the down key for the first time, the focus variable increases by one and the HTML focus goes to the first recommendation. If they press it again, it goes to the focus variable increases by one again, and the HTML focus moves to the second recommendation, and so on. Similarly, when the user presses the up key it decreases the focus by one and moves the HTML focus to the previous recommendation. In addition, there are measures to ensure the focus index does not go outside of the possible range as well as resetting the focus index to zero when the value of the search bar changes.

I am encountering this strange error that when overriding the focus index value, it will function properly only when using the down key, and then when I press the up key it will change to the previous value form before the override. In some case this can cause the focus index to go outside of the range and then crash, since when you type longer searches it narrows down the list of recommendations.

Here are some screen shots of the console output for the focus index when searching only one letter:

enter image description here enter image description here

Now I'll show what happens when I narrow the search and then navigate the recommendations:

enter image description here enter image description here

Here is a working codesandbox link:

https://codesandbox.io/s/jolly-tu-7itsm?fontsize=14&hidenavigation=1&theme=dark

like image 951
JSON_Derulo Avatar asked May 19 '26 18:05

JSON_Derulo


1 Answers

Issue

Ok, it is exactly as I had suspected. You've enclosed some focus value in the "keyup" event handler that is mutated independently of the focus being reset each render cycle in the main component body and useEffect hook when the dependencies update.

There is even a react lint warning in your codesandbox to this effect:

Assignments to the 'focus' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property.

Solution

  1. Store your "focus" value in a React ref so all callback instances and hooks can reference the same stable value.
  2. I suggest also using a separate useEffect hook to manage the "keyup" event listener and cleanup function to remove it when the component unmounts. This will also keep your effect from added a new listener every time the effect callback triggers (your code added a listener each render!!).
  3. I suggest also using a React ref for referencing the "autocomplete__container" container as direct DOM queries and manipulation are considered anti-pattern in React.

Code:

function App() {
  const [value, setValue] = useState("");
  const [items, setItems] = useState([]);
  const [query, setQuery] = useState([]);
  const [formSubmit, setFormSubmit] = useState(false);
  const [completions] = useAutocomplete(value, autocompleteValues);

  const autocompleteContainer = useRef(); // <-- ref to get DOMNode
  const focusRef = useRef(-1); // <-- ref to store stable focus value

  ...

  // Effect hook to manage keyup event handler and cleanup
  useEffect(() => {
    const handleKeypress = (event) => {
      if (event.keyCode === 38 && focusRef.current >= 1) { // <-- focus.current
        focusRef.current -= 1;
        autocompleteContainer.current.childNodes[focusRef.current].focus(); // <-- autocompleteContainer.current
      } else if (
        event.keyCode === 40 &&
        focusRef.current < autocompleteContainer.current.childElementCount - 1
      ) {
        focusRef.current += 1;
        autocompleteContainer.current.childNodes[focusRef.current].focus();
      } else if (
        event.keyCode === 13 &&
        document.activeElement.className !== "form-control"
      ) {
        setValue(document.activeElement.innerHTML);
        setQuery(document.activeElement.innerHTML);
        document.getElementsByClassName(
          "autocomplete__container"
        )[0].style.display = "none";
        setFormSubmit(true);
      }
    };

    window.addEventListener("keyup", handleKeypress); // <-- add listener

    return () => window.removeEventListener("keyup", handleKeypress); // <-- return cleanup function to remove listener
  }, []);

  // Effect hook to reset focus value when list updates
  useEffect(() => {
    focusRef.current = -1;
    console.log("focus reset", focusRef.current);
  }, [value, items]);

  return (
    <div className="App">
      <Form id="search__form" onSubmit={handleSubmit}>
        <Form.Group>
          <InputGroup className="mb-3">
            ...
            <div
              ref={autocompleteContainer} // <-- attach DOM ref here
              className="autocomplete__container"
            >
              {completions.map((val, index) => (
                <p
                  tabIndex={index + 10}
                  key={index}
                  onClick={() => updateInput({ val })}
                >
                  {val}
                </p>
              ))}
            </div>
          </InputGroup>
        </Form.Group>
      </Form>
      <p>{focusRef.current}</p>
    </div>
  );
}

Edit unable-to-override-javascript-variable

like image 171
Drew Reese Avatar answered May 22 '26 07:05

Drew Reese



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!