Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TextInput: autoexpandable #795

Open
gut4 opened this issue Feb 4, 2018 · 12 comments
Open

TextInput: autoexpandable #795

gut4 opened this issue Feb 4, 2018 · 12 comments
Labels
project:react-native-web Issue associated with react-native-web

Comments

@gut4
Copy link

gut4 commented Feb 4, 2018

In react-native TextInput is always autoexpandable. See facebook/react-native@dabb78b

I think simplest way to implement this is to use https://github.com/rpearce/react-expanding-textarea
It's just update textarea height based on el.scrollHeight:

  _handleChange(e) {
    const { onChange } = this.props
    if (onChange) onChange(e)
    this._adjustTextarea(e)
  }


  _adjustTextarea({ target = this.el }) {
    target.style.height = 0
    target.style.height = `${target.scrollHeight}px`
  }
}
@steida
Copy link

steida commented Jul 23, 2018

@gut4 There is a problem with such an implementation. It will break the window scroll occasionally. It's probably much harder to implement with fake height placeholder etc. I had to switch to contentEditable.

@woodpav
Copy link

woodpav commented Jan 20, 2020

Here is how you do it:

import React, { useState } from 'react';
import { TextInput } from 'react-native';

export default function TextField(props) {
  const [scrollHeight, setScrollHeight] = useState(null);
  return (
    <TextInput
      style={{ height: scrollHeight }}
      onChange={(e) => setScrollHeight(e.target.scrollHeight)}
      {...props}
    />
  );
}

@divonelnc
Copy link

divonelnc commented Nov 25, 2020

@woodpav This with multiline works great to increase the size of the text input, but it unfortunately doesn't shrink it when you remove text.

Using the solution proposed by @gut4 worked in both direction for me.

import * as React from 'react';
import { TextInput } from 'react-native';

export default function App() {
  
  const _handleChange = (e) => {
	e.target.style.height = 0
	e.target.style.height = `${e.target.scrollHeight}px`
  };

  return (
    <TextInput
      multiline
      onChange={_handleChange}
    />
  );
}


@staffan-klashed
Copy link

I'm curious if this approach translates across platforms, i.e. can we rely on scrollHeight to be exposed and updated immediately when e.target.style.height is set to zero? My approach is slightly more involved, measuring a hidden Text element to detect shrinking heights, i.e.:

const [height, setHeight] = useState(props.initialHeight);

return(
    <View>
        <TextInput 
            {...props} 
            style={[props.style, {height: height}]}
            onContentSizeChange={e => setHeight(e.nativeEvent.contentSize.height)}
        />
        <Text 
            onLayout={e => e.nativeEvent.layout.height < height && setHeight(e.nativeEvent.layout.height)} 
            style={[props.style, {position: 'absolute', visibility: 'hidden'}]} 
            pointerEvents={'none'}
        >
            {`${props.value || props.defaultValue || props.placeholder || ''}\n`}
        </Text>
    </View>
)

@sreuter
Copy link

sreuter commented Feb 21, 2021

@necolas Are there any plans getting this natively supported by react native web at some point?

Could we use https://www.npmjs.com/package/react-textarea-autosize or a similar approach to get closer to the behaviour of the iOS/Android counterparts of TextInput with multiline enabled?

@MorganTrudeau
Copy link

Ya I have changed the handleContentSizeChange function in dist/exports/TextInput. I want this to happen automatically for all multiline TextInputs in my app. Working for me. Thanks @divonelnc!

  var handleContentSizeChange = React.useCallback(function (hostNode) {
    if (multiline && hostNode != null) {
        const newHeight = hostNode.scrollHeight
        const newWidth = hostNode.scrollWidth;

        hostNode.style.height = 0;
        hostNode.style.height = `${hostNode.scrollHeight}px`;

      if (onContentSizeChange && (newHeight !== dimensions.current.height || newWidth !== dimensions.current.width)) {
        dimensions.current.height = newHeight;
        dimensions.current.width = newWidth;
        onContentSizeChange({
          nativeEvent: {
            contentSize: {
              height: dimensions.current.height,
              width: dimensions.current.width
            }
          }
        });
      }
    }
  }, [multiline, onContentSizeChange]);

@keith-kurak
Copy link

@divonelnc's onChange workaround was almost perfect for me, except that I noticed that onChange() is only called when text is changed by typing, not when the TextInput's value is changed programatically. In our example, the TextInput expands and contracts as text is typed and deleted, but stays expanded after the value is cleared when sending a message in our chat interface is completed. Curious if anyone has any ideas to try. Thank you!

@adamblvck

This comment was marked as off-topic.

@necolas necolas added the project:react-native-web Issue associated with react-native-web label Jul 2, 2022
@clayrisser
Copy link

@woodpav your answer fails to account for border width.

import React, { useState } from 'react';
import { TextInput } from 'react-native';

export default function TextField(props) {
  const [scrollHeight, setScrollHeight] = useState(null);
  return (
    <TextInput
      style={{ height: scrollHeight }}
      onChange={(e) => setScrollHeight(e.target.offsetHeight - e.target.clientHeight + e.target.scrollHeight)}
      {...props}
    />
  );
}

@willstepp
Copy link

I've taken what's here and adapted it for my own use-case. It seems to work well, resizing upon initialization and any subsequent changes:

import * as React from 'react';
import { TextInput } from 'react-native';

export default function App() {
  
  const adjustTextInputSize = (evt) => {
    const el = evt?.target || evt?.nativeEvent?.target;
    if (el) {
      el.style.height = 0;
      const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight;
      el.style.height = `${newHeight}px`;
    }
  };

  return (
    <TextInput
      multiline
      onChange={adjustTextInputSize}
      onLayout={adjustTextInputSize}
    />
  );
}

@arhipy97
Copy link

arhipy97 commented Jun 6, 2023

@willstepp, your solution works perfectly, but I am struggling with one case on the web expo app.
I use onSubmitEditing method in TextInput, and after pressing the "Enter" button, TextInput doesn't resize.
Don't you have any ideas on how to handle it?

Screen.Recording.2023-06-06.at.21.41.33.mov

@aryo
Copy link
Contributor

aryo commented Sep 27, 2023

@arhipy97 you can extract this part into a fn:

const resetHeight = el => {
  if (el) {
    el.style.height = 0;
    const newHeight = el.offsetHeight - el.clientHeight + el.scrollHeight;
    el.style.height = `${newHeight}px`;
  }
}

and pass in a ref to the TextInput so you can either hook into the onSubmit or use an effect to reset it:

useEffect(() => {
  if (!value) {
    resetHeight(inputRef.current);
  }
}, [value]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
project:react-native-web Issue associated with react-native-web
Projects
None yet
Development

No branches or pull requests