Kittichote Chain
Kittichote Chain

Reputation: 681

How to make input field's height responsive to the content?

It is a MERN stack app. I tried to make the height of a textarea responsive to the content using this script.

It looks like the external javascript file is working because I tried putting alert in the for loop and it did work. So I tried putting alert inside the OnInput() function but the alert was not called. Therefore, I think something is wrong with this function.

index.html

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>

  <script src="/main.js"></script>
</body>

main.js

var tx = document.getElementsByClassName('comment-area-responsive');

for (var i = 0; i < tx.length + 1; i++) {
  //   alert(i);
  tx[i].setAttribute(
    'style',
    'height:' + tx[i].scrollHeight + 'px;overflow-y:hidden;'
  );
  tx[i].addEventListener('change', OnInput, false);
}

function OnInput() {
  this.style.height = 'auto';
  alert('hi2');
  this.style.height = this.scrollHeight + 'px';
}

page

<textarea 
  className='comment-area-responsive' 
  name='title' 
  placeholder='What do you think?' 
  value={text} onChange={e => setText(e.target.value)} 
  required
/>

Upvotes: 2

Views: 1298

Answers (2)

Vandesh
Vandesh

Reputation: 6904

The script works as intended after removing a few problems from your code -

  1. Instead of change event, you should listen to the textarea's input event.

  2. textarea is a container, so you close it using a closing tag </textarea> and not a self closing tag. Read more

  3. Maybe you're using some other library, so make sure that you really need value={text} and onChange={e => setText(e.target.value)} attributes on the textarea. If you added them only for the purpose of this script, then they're not needed as you can see in the code below.

Run the snippet below to check that it works correctly after fixing all the above problems

var tx = document.getElementsByClassName('comment-area-responsive');
for (var i = 0; i < tx.length; i++) {
  tx[i].setAttribute('style', 'height:' + (tx[i].scrollHeight) + 'px;overflow-y:hidden;');
  tx[i].addEventListener("input", OnInput, false);
}

function OnInput() {
  this.style.height = 'auto';
  this.style.height = (this.scrollHeight) + 'px';
}
<div>
<textarea 
  class='comment-area-responsive' 
  name='title' 
  placeholder='What do you think?'  
  required
  ></textarea>  
</div>

Update : For getting it to work with React, since React treats onChange and onInput in the same way, you can do it inside your onChange handler, like -

const setText = (val) => {
      this.setState({text: val});
      var tx = document.getElementsByClassName('comment-area-responsive');
      for (var i = 0; i < tx.length; i++) {
        tx[i].setAttribute('style', 'height:' + (tx[i].scrollHeight) + 'px;overflow-y:hidden;');
      }
    }

...

    <textarea 
      className='comment-area-responsive' 
      name='title' 
      placeholder='What do you think?' 
      value={text} onChange={e => setText(e.target.value)} 
      required>
    </textarea>

Here's a working Stackblitz to play around.

Update 2: For getting it to work with React using some component lib like antd without changing the native component implementation, just add all the code logic inside the useEffect:

// You'll need to pick `value` for useEffect's dependencies array
function TextAreaComponent({ value, ...props }) {
  const innerRef = useRef(null)

  useEffect(() => {
      // default antd ref doesn't stores the `textarea` element itself
      // instead, you need to get it from inner ref prop 
      // (this is a lib specific approach)
      const textarea = innerRef.current?.resizableTextArea?.textArea

      const onType = (e: Event) => {
        const target = e.target

        target.style.height = 'auto'
        target.style.height = `${target.scrollHeight}px`
      }

      if (textarea) {
        textarea.setAttribute(
          'style',
          'height:' + textarea.scrollHeight + 'px;overflow-y:hidden;'
        )

        textarea.addEventListener('input', onType, false)
      }

      return () => {
        if (textarea) {
          textarea.removeEventListener('input', onType)
        }
    }, [value])

  return (
    <Input.TextArea ref={innerRef} value={value} {...props} />
  )
}

Extra: If you need to pass any external ref to your component and keep using innerRef as shown above, take a look into the useImperativeHandle hook doc.

Upvotes: 2

DreamTeK
DreamTeK

Reputation: 34297

I think the issue lies with your textarea.

<textarea 
  className='comment-area-responsive' 
  name='title' 
  placeholder='What do you think?' 
  value={text} onChange={e => setText(e.target.value)} 
  required
/>

The textarea tag must be implicitly closed.

<textarea 
  className='comment-area-responsive' 
  name='title' 
  placeholder='What do you think?' 
  value={text} onChange={e => setText(e.target.value)} 
  required
></textarea>

Can you try and see if this fixes your issue?

Upvotes: 0

Related Questions