4

I need an input that will have min width and max width and it will grow and shrink according to its content

3 Answers 3

3

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>

Sign up to request clarification or add additional context in comments.

Comments

1
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

1

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 :

  1. making the p and input content same (including spaces with whitespace-pre)
  2. hiding the p tag (its just there to extend the width of parent div)
  3. input is absolute so it will scale to its parent width
  4. 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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.