I need an input that will have min width and max width and it will grow and shrink according to its content
3 Answers
You have to have state. It can be passed as a parameter (hoisted state, when a state is at the upper level) or be right in the component
Then just use the length of the input-state
New solution
const Input = (props) => {
const [value, changeValue] = React.useState('');
return (
<input
style={{ width: Math.min(Math.max(value.length, 2), 50) + 'ch' }}
value={value}
onChange={(event) => {
changeValue(event.target.value);
}}
/>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(<Input />);
input {
font-family: "Roboto", monospace;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Math.min and Math.Max are used to have size between min(2) and max(20). Without monospace font-family you can get other results
Old solution
const {useState} = React;
const Input = (props) => {
const [value, changeValue] = useState('');
return (
<input
size={Math.min(Math.max(value.length, 2), 20)}
value={value}
onChange={(event) => {changeValue(event.target.value);}}
/>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Input />
);
input {
font-family: "Roboto", monospace;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Comments
import React, {
type CSSProperties,
type InputHTMLAttributes,
useEffect,
useRef,
useState
} from 'react'
const sizerStyle: CSSProperties = {
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden',
height: 0,
overflow: 'scroll',
whiteSpace: 'pre'
}
export default function AutosizeInput(props: InputHTMLAttributes<HTMLInputElement>) {
const originMinWidth = props.style?.minWidth
const originMaxWidth = props.style?.maxWidth
const inputRef = useRef<HTMLInputElement>(null)
const textRef = useRef<HTMLDivElement>(null)
const [width, setWidth] = useState(1)
const [refStyle, setRefStyle] = useState({})
useEffect(() => {
const s = getComputedStyle(inputRef.current!)
setRefStyle({
fontSize: s.fontSize,
fontFamily: s.fontFamily,
fontWeight: s.fontWeight,
fontStyle: s.fontStyle,
letterSpacing: s.letterSpacing,
textTransform: s.textTransform
})
}, [props.className, props.style])
useEffect(() => {
setWidth(textRef.current?.clientWidth || 1)
}, [props.value, refStyle])
const textStyle = {...refStyle, ...sizerStyle, display: 'inline-block'}
if (originMinWidth) textStyle.minWidth = originMinWidth
if (originMaxWidth) textStyle.maxWidth = originMaxWidth
return <>
<input ref={inputRef} {...props} style={{...props.style, width: width + 'px'}}/>
<div ref={textRef} style={textStyle}>
{props.placeholder}
<br/>
{props.value}
</div>
</>
}
import React from 'react'
import ReactDOM from 'react-dom/client'
import AutosizeInput from './AutosizeInput'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<AutosizeInput
style={{minWidth: '50px', maxWidth: '100px'}}
placeholder="Hello"/>
</React.StrictMode>
)
Comments
Alright, it took me a while to figure it out. I found a similar component on resume.io editor (resume title input) After studying the code, I found out that we can use a parent div over an input and make the input absolute positioned. Along with it create another paragraph inside the parent div (just to scale the div) here's some code that'll help
const AutoSizeInput = () => {
const [text, setText] = useState("Default Text"); // text
return (
<div className="relative h-8 max-w-full">
<input
value={text}
onChange={(e)=>setText(e.target.value)}
className="absolute top-0 left-0 w-full h-full"
/>
<p className="opacity-0 invisible whitespace-pre">{text}</p>
</div>
)
}
export default AutoSizeInput
What we are doing here is :
- making the p and input content same (including spaces with whitespace-pre)
- hiding the p tag (its just there to extend the width of parent div)
- input is absolute so it will scale to its parent width
- additionally, you can give minimum width to the parent div
Also, I am using TailwindCSS here, but you can do it however you want. Hope you find this solution useful