Joseph K.
Joseph K.

Reputation: 1223

Listening for useState default value event

I would like to know how to get the updated labelRef within a function, namely inputBlur()

import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [inputValue, setInputValue] = useState("Type...");
  const [labelWidth, setLabelWidth] = useState();
  const labelRef = useRef();

  const inputChange = e => {
    setInputValue(e.target.value);
    setLabelWidth(labelRef.current.offsetWidth);
  };

  const inputBlur = e => {
    const trimmed = e.target.value.trim();
    if (trimmed) {
      setInputValue(trimmed);
    } else {
      setInputValue("Type...");
    }
    setLabelWidth(labelRef.current.offsetWidth);
  };

  useEffect(() => {
    setLabelWidth(labelRef.current.offsetWidth);
  }, []);
  return (
    <div className="App">
      <p>Type below to see if input and div is the same width</p>
      <div ref={labelRef}>{inputValue}</div>
      <br />
      <input
        value={inputValue}
        onChange={e => inputChange(e)}
        onBlur={e => inputBlur(e)}
        style={{ width: `${labelWidth}px` }}
      />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

To explain this example a bit, there is an input field that changes its width by filling a div with the same text. Currently, it doesn't work right when the input field is empty followed by an onblur event. The input field should be filled with the default useState value, "Type...", but the input field width is 0.

The problem I see is that in inputBlur function, after triggering setInputValue, the line labelRef.current.offsetWidth still reflects the old element, since it has not yet re-rendered that element.

Here is the link to the codesandbox of this example to try replicating this problem.

My issue comes from the lack of understanding the flow of react hook. I need to capture the event after useState has updated the element with its default value, and then calculate the width.

Note: I do not want the input to automatically fill in the empty input field with the default value while still in focus. It must be filled in on an onBlur() event.

Upvotes: 0

Views: 231

Answers (2)

skyboyer
skyboyer

Reputation: 23763

Not really sure if I got your question right, so it's like an assumption.

The root cause: you are calling both setInputValue and setLabelWidth in the same place - input handler. But setInputValue will update label with new value only on next render. So it takes labelWidth for previous value.

It happens not only when you emptify value - you may see that input's width changes inconsistently while typing.

Make it separate useEffect:

  const inputBlur = e => {
    const trimmed = e.target.value;
    if (trimmed) {
      setInputValue(trimmed);
    } else {
      setInputValue("Type...");
    }
  };

  useEffect(() => {
    setLabelWidth(labelRef.current.offsetWidth);
  }, [inputValue])

Upvotes: 1

Peter
Peter

Reputation: 707

in inBlur function, you are updatinglabeWidth with the div's current offsetWidth which is always 0 after an empty input followed by an onBlur event. Change to this: setLabelWidth(labelWidth);

Another way is moving ref={labelRef} to input element: <input ref={labelRef} />. See the official docs

Upvotes: 0

Related Questions