Reputation: 1223
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
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
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