7

I am developing react-native application. In that, We are showing some description on Text, It may be number of lines.

So, If data has more than 3 lines, I have to show More and Less if it's expanded.

        <FlatList
          style={styles.faltList}
          showsVerticalScrollIndicator
          data={data}
          extraData={this.state}
          renderItem={({ item, index }) => (
            <View style={styles.flatListCell}>
                <Text style={styles.description}>{item.description}</Text>
              </View>
            </View>
          )
          }
          ItemSeparatorComponent={() => (
            <View style={{ height: 10}} />
          )}
        />

I have found react-native-view-more-text library, But I would like to implement it by custom code.

Note: I am displaying that Text in FlatList.

Any suggestions?

Pavel Chuchuva
  • 22,633
  • 10
  • 99
  • 115

7 Answers7

40

I tried in this way, Hope it helps to you and others!

const postTextContent = (props) => {
const [textShown, setTextShown] = useState(false); //To show ur remaining Text
const [lengthMore,setLengthMore] = useState(false); //to show the "Read more & Less Line"
const toggleNumberOfLines = () => { //To toggle the show text or hide it
    setTextShown(!textShown);
}

const onTextLayout = useCallback(e =>{
    setLengthMore(e.nativeEvent.lines.length >=4); //to check the text is more than 4 lines or not
    // console.log(e.nativeEvent);
},[]);
    
  return (
      <View style={styles.mainContainer}>
          <Text
              onTextLayout={onTextLayout}
              numberOfLines={textShown ? undefined : 4}
              style={{ lineHeight: 21 }}>{Your Long Text}</Text>

              {
                  lengthMore ? <Text
                  onPress={toggleNumberOfLines}
                  style={{ lineHeight: 21, marginTop: 10 }}>{textShown ? 'Read less...' : 'Read more...'}</Text>
                  :null
              }
      </View>
  )
}
高鵬翔
  • 1,997
  • 2
  • 8
  • 21
Ankit
  • 411
  • 4
  • 4
  • Worked fine! Thanks – Patrissol Kenfack Jun 05 '20 at 23:22
  • 3
    What if I want to inline Read Less or Read More? To be precise, after ... are added by ellipses, I want to add Read More or Read Less? How this solution would work in that case – Mehroze Yaqoob Jul 30 '20 at 10:25
  • That works fine,Thanks. just notice that the condition must be greater than numberOfLines and not greater or equal. – ABDELKHALEK ZELLAT Sep 15 '20 at 22:22
  • @Ankit The onTextLayout doesn't work to get the length. if i make lengthMore state as true, then this works but the Read more text is present permanently, no matter what the length of text is.... Any idea how to make this work? https://snack.expo.dev/vhn0ZM3Pn – Jayesh Oct 07 '21 at 16:23
  • For some insane reason, the above is not working in Snack but is working in AVD. – Jayesh Nov 15 '21 at 05:10
  • No complications. Just copy and paste. Working source. Thanks! – M.Kalidass Jul 06 '22 at 13:46
4

You can simply use numberOfLines, which is a <Text> prop:

Used to truncate the text with an ellipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number.

And, obviously, an adequate logic handler to save in your state which text is shown and which is truncated.

Let's see an example I've just created:

state = {
    textShown: -1,
  };

  toggleNumberOfLines = index => {
    this.setState({
      textShown: this.state.textShown === index ? -1 : index,
    });
  };

  render() {
    return (
      <View style={styles.container}>
        <FlatList
          data={[
            { key: 'a', description: longText },
            { key: 'b', description: longText },
            { key: 'c', description: longText },
          ]}
          renderItem={({ item, index }) => (
            <View style={styles.flatListCell}>
              <Text
                numberOfLines={this.state.textShown === index ? undefined : 3}
                style={styles.description}>
                {longText}
              </Text>
              <Text
                onPress={() => this.toggleNumberOfLines(index)}
                style={{ color: 'red' }}>
                {this.state.textShown === index ? 'read less...' : 'read more...'}
              </Text>
            </View>
          )}
        />
      </View>
    );
  }

Here I use state to save the index of the element in the FlatList which is shown. If none is shown, then the value saved is -1.

You can try its behavior in this snack, that (I hope) reproduces your case. Let me know if this is what you're looking for. (Hi, Anilkumar, we've already met :) )

Milore
  • 1,672
  • 1
  • 15
  • 20
4

The first implementation is close but the problem is that the Read More button shows when the text equals 3 lines which it should not because there is no more text. I fixed it by updating the number of lines in state as well as checking if the text had been shown or not.

const ReadMoreText = ({ readMoreStyle, text, textStyle }) => {
  const [showMoreButton, setShowMoreButton] = useState(false);
  const [textShown, setTextShown] = useState(false);
  const [numLines, setNumLines] = useState(undefined);

  const toggleTextShown = () => {
    setTextShown(!textShown);
  };

  useEffect(() => {
    setNumLines(textShown ? undefined : 3);
  }, [textShown]);

  const onTextLayout = useCallback(
    (e) => {
      if (e.nativeEvent.lines.length > 3 && !textShown) {
        setShowMoreButton(true);
        setNumLines(3);
      }
    },
    [textShown],
  );

  return (
    <>
      <Text onTextLayout={onTextLayout} numberOfLines={numLines} style={textStyle} ellipsizeMode="tail">
        {text}
      </Text>

      {showMoreButton ? (
        <Text onPress={toggleTextShown} style={readMoreStyle}>
          {textShown ? 'Read Less' : 'Read More'}
        </Text>
      ) : null}
    </>
  );
};
1

take a look over my solution, I did not use text lines, I used text length instead.

import React, {useState} from 'react';
import {Text, View, Image, TouchableOpacity, StyleSheet} from 'react-native';

const PostContent = () => {
  const postDescription =
    "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
  const [showMore, setShowMore] = useState(false);

  return (
    <View style={styles.postContentContainer}>
      {postDescription.length > 120 ? (
        showMore ? (
          <TouchableOpacity onPress={() => setShowMore(!showMore)}>
            <Text style={styles.postDescription}>{postDescription}</Text>
            <Text style={styles.seeMore}>Show less</Text>
          </TouchableOpacity>
        ) : (
          <TouchableOpacity onPress={() => setShowMore(!showMore)}>
            <Text style={styles.postDescription}>
              {`${postDescription.slice(0, 120)}... `}
            </Text>
            <Text style={styles.seeMore}>Show more</Text>
          </TouchableOpacity>
        )
      ) : (
        <Text style={styles.postDescription}>{postDescription}</Text>
      )}
    </View>
  );
};

export default PostContent;

const styles = StyleSheet.create({
  postContentContainer: {
    // borderWidth: 1,
    // borderColor: 'red',
    flexDirection: 'column',
  },

  postMedia: {
    //borderWidth: 1,
    //borderColor: 'red',
    width: '100%',
    height: 280,
    resizeMode: 'cover',
  },

  postDescription: {
    paddingTop: 15,
    paddingHorizontal: 15,
    color: colors.text.black,
    fontFamily: fonts.family.body,
    fontSize: fonts.fontSizes.button,
    fontWeight: fonts.fontWeights.thin,
  },

  seeMore: {
    paddingHorizontal: 15,
    color: colors.text.grey,
    fontStyle: 'italic',
    textDecorationLine: 'underline',
    fontFamily: fonts.family.body,
    fontSize: fonts.fontSizes.button,
    fontWeight: fonts.fontWeights.thin,
  },
});
  • In general this is a good idea for a normal text but consider following: If there is a text starting with 30 line breaks this isn't a optimal solution because than you would have a length of 0 or 1 but the text is not truncated – Henrik Jan 03 '23 at 22:05
1

A little late to the party, but I have developed a very optimal, lightweight solution to this. I call it react-native-more-or-less

Here is an example of how to use it:

import { MoreOrLess } from "react-native-more-or-less";

// ...

export default function App() {
  return (
    <MoreOrLess numberOfLines={3} textComponent={CustomText}>
      Lorem Ipsum is simply dummy text of the printing and typesetting industry.
      Lorem Ipsum has been the industry's standard dummy text ever since the
      1500s, when an unknown printer took a galley of type and scrambled it to
      make a type specimen book. It has survived not only five centuries, but
      also the leap into electronic typesetting, remaining essentially
      unchanged. It was popularised in the 1960s with the release of Letraset
      sheets containing Lorem Ipsum passages, and more recently with desktop
      publishing software like Aldus PageMaker including versions of Lorem
      Ipsum.
    </MoreOrLess>
  );
}

Stats: https://bundlephobia.com/package/react-native-more-or-less@0.3.0

Ren
  • 4,594
  • 9
  • 33
  • 61
-1
import React from 'react';
import PropTypes from 'prop-types';
import AnchorText from '../AnchorText';

import { StyledContainer, RegularText } from './styles';

export default class Description extends React.Component {
      constructor(props) {
super(props);
this.state = {
  showMore: true,
};
}

 onClick = () => {
this.setState({
  showMore: false,
});
};

 render() {
const { description } = this.props;
const { showMore } = this.state;
if (!description) return null;

return (
  <StyledContainer FD={'column'}>
    {showMore ? (
      <RegularText MT={1}>
        {description.slice(0, 150)}{' '}
        {description.length > 150 && (
          <AnchorText onClick={this.onClick} label=" .. Read more" />
        )}{' '}
      </RegularText>
    ) : (
        <RegularText MT={1}>{description}</RegularText>
    )}
  </StyledContainer>
);
 }
} 

Description.propTypes = {
 description: PropTypes.string,
 };

Check out this Widget

Aurangzaib Rana
  • 4,028
  • 1
  • 14
  • 23
-2

check output of this code

state = {
    textLenth: null,
    numberOfLines: 3,
}
handleSeeMore = () => {
    this.state.textLenth
    ? this.setState({numberOfLines: 0})
    : this.setState({numberOfLines: 3});
};

     <Text
     numberOfLines={this.state.numberOfLines}
     onPress={() => this.handleSeeMore()}
     ellipsizeMode="middle"
     onTextLayout={({nativeEvent: {lines}}) =>
     this.setState({textLenth: lines.length === 3})
     }>
         This Gig Take a glance at the showcase of our artistic work:
         Modern and Trendy Logo Artworkslkjfkljf ksnfksfnsf Mascot &
         Custom Logo efdfg Artworks:lk knnk 'Explore the
         ultimate Experience..!' To fulfill your designing needs, 
         Make us Graphically Yours...!! Why Team StrideInIt? We 
         believe in our
     {'                      '}
         {this.state.textLenth && (
         <Text
         color="red"
         onPress={() => this.setState({numberOfLines: 0})}>
             see more
         </Text>
         )}
     </Text>
Cem
  • 428
  • 4
  • 14