javascriptreactjsreact-hooksresize

Auto-scaling input to width of value in React


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?


Solution

  • 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.