Reputation: 2098
I want to have an input whose width adapts to fit its content.
I'm trying to implement this answer to a similar question, but using React:
import React, { useState } from 'react';
export default () => {
const [content, setContent] = useState('');
const [width, setWidth] = useState(0);
const changeHandler = evt => {
setContent(evt.target.value);
};
return (
<wrapper>
<span id="hide">{content}</span>
<input type="text" autoFocus style={{ width }} onChange={changeHandler} />
</wrapper>
);
};
The problem is I don't know how to then query the width of the span, in order to then change the width of the input (using setWidth
).
How can I achieve this?
Upvotes: 16
Views: 30886
Reputation: 1
Another option without using extra hidding element:
const GrowingInput = () => {
const inputRef = React.useRef(null);
const handleChange = () => {
inputRef.current.style.width = "0";
inputRef.current.style.width = `${inputRef.current.scrollWidth}px`;
};
return <input ref={inputRef} style={{width: 0}} autoFocus onChange={handleChange} />
};
const App = () => <p>Lorem ipsum {<GrowingInput />} egestas arcu. </p>;
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 0
Reputation: 2098
After a lot of fiddling around, I found a solution!
import React, { useState, useRef, useEffect } from 'react';
export default () => {
const [content, setContent] = useState('');
const [width, setWidth] = useState(0);
const span = useRef();
useEffect(() => {
setWidth(span.current.offsetWidth);
}, [content]);
const changeHandler = evt => {
setContent(evt.target.value);
};
return (
<wrapper>
<span id="hide" ref={span}>{content}</span>
<input type="text" style={{ width }} autoFocus onChange={changeHandler} />
</wrapper>
);
};
To get a reference to the #hide
span I employ useRef
. Then, the width
state variable can be updated via the function defined inside useEffect
, which gets called everytime content
changes.
I also had to switch the display: none
in the css of #hide
for position: absolute
and opacity: 0
, as otherwise targetRef.current.offsetWidth
would always evaluate to 0.
Here's a working demo.
Upvotes: 30
Reputation: 429
Here is the simplest solution I've found.
You create a function that you'll use on change
const handleChangeAndSize = (ev: ChangeEvent<HTMLInputElement>) => {
const target = ev.target;
target.style.width = '60px';
target.style.width = `${target.scrollWidth}px`;
handleChange(ev);
};
Then you use it as a regular function in your component
<input type='text' onChange={handleChangeAndSize}/>
The style.width = 60px will allow to resize the input when shrinking, and the target.scrollWidth will watch the 'scrollable width' on x axis and set it as width.
Nb: credit to this guy: https://www.youtube.com/watch?v=87wfMZ56egU
Upvotes: 3
Reputation: 43
Found out a trick using Refs in react.
style={{ width: inputRef.current ? inputRef.current.value.length + 'ch' : 'auto' }}
And set the ref={inputRef}
for the element.
Do remember to set the min-width for the input in your CSS.
Upvotes: 2
Reputation: 1925
Well, this was interesting enough! I tried a few different ideas that I had, but none of them worked perfectly - especially not if they were to be written in a somewhat respectable code.
I found this post however and decided to try that out. https://stackoverflow.com/a/43488899/3293843
I am sure there are flaws with it, one for example is that it does act funny unless I use a monospaced font. But maybe there are some css tricks to get around that?
// Normally I'd go for ES6 imports, but to make it run as a StackOverflow snippet I had to do it this way
const { useState, useRef } = React;
const GrowingInput = () => {
const [width, setWidth] = useState(0);
const changeHandler = evt => {
setWidth(evt.target.value.length);
};
return (
<input style={{ width: width +'ch'}} type="text" autoFocus onChange={changeHandler} />
)
};
const App = () => {
return (
<p>Lorem ipsum {<GrowingInput />} egestas arcu.</p>
);
};
// Render it
ReactDOM.render(<App />, document.getElementById("react"));
input {
font-family: Courier;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Have you considered using a contenteditable
instead?
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content
Upvotes: 8