React-native: [Text] How to show "Read more" when using numberOfLines property?

Created on 30 Aug 2015  ·  49Comments  ·  Source: facebook/react-native

Hi all,

I have a long text with <Text> element and I use numberOfLines to truncate the text and show "..." at the end.

I also want to show Read More link If the text was truncated and while clicking on it will show the full text.

How can I know if the text was truncated or not?

Thanks!

Ran Commands Stale Enhancement

Most helpful comment

made this lib based off of the approach of initially rendering with unbounded number of lines, then adjusting on the next frame and comparing height. check out the source, it's pretty straightforward.

https://github.com/exponentjs/react-native-read-more

Try it out here: https://getexponent.com/@community/read-more-example

All 49 comments

Hi there!

There's no way to know if it has been truncated, maybe it would be nice to have that option available although I'm not sure what the best way to expose it would be.

// Non-existent API that might work
<Text numberOfLines={3} renderTruncatedFooter={() => <ReadMoreButton />}>Some text</Text>

For now, you could just truncate the text manually when it exceeds some number of characters that you define, and add the the Read more button manually.

cc @ide

@brentvatne Thanks for the comment! I sure it will be useful to have that API.

I found React Elipsify which looks great but only work with HTML. It's too bad that we have duplicate components only because the rendering target is different

Is there a standard way to share components between React for Web and React Native?
Maybe have <View> & <Text> that will translate to <div> & <span> ?

@ranyefet - alas, there is no standard way to do that right now :(

I'm interested in seeing renderTruncatedFooter get implemented. :smile:

cc @ide

I got this worked by call the measure function. At first set the numberOfLines=0 , then in the measure funtion, I can get the height of the view, if the value of height is bigger than my setting value(in my case 100 pt for 4 lines)。it means there are some word need to truncate,so I set numberOfLines to 4,and show the show more button.

Ideally, you should avoid manual text layout. But it's sometimes required. Thus, I implemented some helper functions on NativeModules.TextManager.

How you use these helpers to achieve truncation is up to you. But I will eventually report back here with details about doing so.


Use TextManager.computeWidth() for single-line Text components.

See the implementation.

TextManager.computeWidth("any string", {
  fontFamily: String,
  fontSize: Number,
  fontWeight: Number,
  fontStyle: String,
  letterSpacing: Number,
}, function(width) {
  // The returned number represents the total width of the given string.
});

Use TextManager.computeLines() for multi-line Text components.

See the implementation.

TextManager.computeLines("any string", {
  width: Number,
  fontFamily: String,
  fontSize: Number,
  fontWeight: Number,
  fontStyle: String,
  letterSpacing: Number,
  lineBreakMode: String, // This needs other commits to be supported.
}, function(lines) {
  // The returned array contains each line of text.
});

@facebook-github-bot stack-overflow

Hey @ranyefet and thanks for posting this! @jsierles tells me this issue looks like a question that would be best asked on StackOverflow. StackOverflow is amazing for Q&A: it has a reputation system, voting, the ability to mark a question as answered. Because of the reputation system it is likely the community will see and answer your question there. This also helps us use the GitHub bug tracker for bugs only. Will close this as this is really a question that should be asked on SO.

@aleclarson - that looks neat!

I reopened this issue because I'm not convinced we have a very good general solution to this common problem yet.

Especially when I'm not seeing an obvious way to animate Text view's numberOfLines, for a "Read more..." animated expansion on tap. The best I can do would be hiding overflowed text in a hidden second Text view, and show/expand that (with animation) on tapping "..." in the initial truncated Text block. But then I end up with 2 discontinuous text blocks, which is not ideal.

I propose adding a "isTruncated" prop (bool) to <Text>, and a complementary function for animating numberOfLines from X to Y. If Y>X it'd be a text expand, if not it'd be a collapse. Truncating large text blocks and handling its revealing is a pretty common app interaction.

Otherwise, if there's a way to implement this kind of "Read more..." / Close interaction for large text blocks like the Web equivalent, I'd most appreciate a direction on how.

@fungilation I went ahead and posted your feature request on Product Pains. :+1:

@aleclarson Your implementation links don't work anymore. Could you provide them again, I'm very interested.

@fantattitude Fixed! 👍

@aleclarson Do you think it'd be possible to add the method to a category (then not having to change the react-native I use to a fork I'd create) ?

@fantattitude I don't see why not, but I haven't tried. 😄

@aleclarson I can confirm, it works, just had to add Header Search Path to RCTTextManager. But now I'm stuck because my render happens before computeLines finish and changes my state even though I do it in componentDidMount

@fantattitude You'll want to hide the element until computeLines finishes. Or you could call computeLines in a container component; then render the dependent component after computeLines finishes.

👍

how to resolve it?

made this lib based off of the approach of initially rendering with unbounded number of lines, then adjusting on the next frame and comparing height. check out the source, it's pretty straightforward.

https://github.com/exponentjs/react-native-read-more

Try it out here: https://getexponent.com/@community/read-more-example

Looks perfect!

Great solution! Promise is more delicate than mine.

Hey Brentvatne, looks like your solution is frame lost, we tried it on our application with react-native-parsed-text and it's not fast enough :/ 😭

@Liroo You could make the text with opacity 0 and then fade it in after measurements. Probably there should be a callback for that

Yes sure, it's fast enough for one text maybe but we're using it in a timeline and there is potentially many texts to truncate, we have a frame with full text, a frame with truncated text and a frame with readMore button, it's really disturbing

I wrote this plugin to display show more/less. Basically, I set numberOfLines=null, got the Height, then set back to provided number to know if the text was cut off or not.

https://github.com/nlt2390/react-native-view-more-text

Yes sure, it's fast enough for one text maybe but we're using it in a timeline and there is potentially many texts to truncate, we have a frame with full text, a frame with truncated text and a frame with readMore button, it's really disturbing

Sounds like you need a native solution for this then! Agreed that the solution I posted isn't ideal for this use case

Would it be helpful to expose a class method of Text for checking whether the text is currently being truncated or not?

Android exposes a method that would allow us to check whether the text is being truncated. I could whip up a fix for Android if someone wants to tackle the iOS side!

@brentvatne I tried your solution and it works for most of what I need, but I was wondering if it's possible to put the read more button on the last line (instead of below the original text) in the event of truncation. Is that only possible right now if I truncate based on the number of characters?

@aleclarson Your methods don't seem to exist on RCTTextManager (I'm looking here) Did they get pulled? Do we have any way of measuring the size of text from JavaScript?

@benvium Fixed the links on my comment, again. Sorry about that. Those implementations are not currently in RN core.

@aleclarson Thank you. Looks like I can easily pull that implementation into a new plugin. I don't suppose you have an android implementation?

@benvium Nope, only working on iOS atm. Maybe someone else can contribute. 👍

@brentvatne react-native-read-more doesn't work for state based text. If I change text state it doesn't trigger show/hide button :(

@vvusts - feel free to fork the project or submit a PR to add support for that!

As @Liroo metioned, I am facing the same issue when I'm using the expo/react-native-read-more-text. In my case, I need to render a list with many items, each item contains a photo and a long text. Every time, when I refresh the FlatList or paginate it, I will see three steps to render:

  1. a frame with full text
  2. a frame with truncated text
  3. a frame with readMore button

This three steps sometimes make the FlatList automatically scroll a few pixels or even a half screen. It is very annoying, I think the truncated text should be rendered before the component is shown up.

@gameboyVito: it's not ideal, but if you don't want to use @aleclarson's solutions and don't have the experience to implement what you want as a custom native component, I would suggest doing the truncation based on the number of characters (we actually do this internally in a few places).

We should definitely solve this in the core though since it is a pretty common use-case. I think custom ellipses and ellipses onPress handler support would do it? We should also add text measurement functions because they are generally useful. Not sure when we would get to this though, but would appreciate PRs...

renderTruncatedFooter?

@Liqiankun what does this mean? That doesn't exist. Are you suggesting thats the name that it should be implemented under?

renderTruncatedFooter?

This feature must be done inside of NativeModule, instead of measuring the size of text through JS. And, it should provide a API to control whether to show the truncated text or the full text.

Speaking of NativeModule, does anyone know how to make a bridge between GTMFadeTruncatingLabel and RN's ?

Reference: Fade out end of text label strings that don't fit (instead of truncate)

Thanks!

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

as @brentvatne say, we can use this attribute numberOfLines={3} in the Text Component. for example..

@gameboyVito Hi! I have the same problem, have you found a solution?

@Ilario17 You may find solution for your case using tricks described here http://cmichel.io/how-to-get-the-size-of-a-react-native-view-dynamically/

@GNarek the problem is that I should add "Read more" in a Flatlist, So when you scroll down there ins't problem, but when you scroll up the recreated objects have a different height and then the table automatically shrinks giving a very bad feedback to the user

Just store/fetch the data in a cache outside of the component, like redux or whatever you have for client state. Make sure the key you use is sufficiently unique that you won’t get collisions, but is reproducible when the component remounts.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

@brentvatne: Sometime calling the _handleTextReady Function taking time to called and because of that Read More button appears very late.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jlongster picture jlongster  ·  3Comments

lazywei picture lazywei  ·  3Comments

phongyewtong picture phongyewtong  ·  3Comments

despairblue picture despairblue  ·  3Comments

ghost picture ghost  ·  3Comments