Search code examples
reactjstypescriptsetstate

Maintaining same state variable to implement Show More/Less functionality


I am having a page with multiple cards, where each card has a list of items.I have been trying out the implementation of Show More/ Show Less implementation on each of the card. For a single card, I am getting it right by adding two properties to the state object. But if I implement the same for all the cards, I end up adding more state object properties one for each card. Looking out for a better way, where I can use one common to handle all cards Show More/Less implementation

Just need an efficient approach to achieve this. Help would be appreciated

Component Code

Example with only two cards

import * as React from 'react';
interface IState{
  arr1:[],
  arr2: [],
  arr1ExpandFlag: boolean, // For updating Button label(More/Less)
  arr2ExpandFlag : boolean
  arr1temsToShow: number,  // Number of items to show
  arr2itemsToShow: number 
  //I will end up adding two variables one for each card
}
export default class App extends React.Component<{},IState> {

  constructor(props:any){
    super(props);
    this.state = {
      arr1 :[],
      arr2 :[],
      arr1ExpandFlag: false,
      arr1ExpandFlag: false,
      arr1itemsToShow: 3,
      arr2itemsToShow: 3,
    }
  }

  showMore = (type: string) => { // end up adding if conditions
    if(type === 'arr1')
    {
      (this.state.arr1itemsToShow === 3) ? 
      (this.setState({ arr1itemsToShow: this.state.arr1.length, arr1ExpandFlag: true })):
      (this.setState({ arr1itemsToShow: 3,arr1ExpandFlag: false}))
    }
    if(type === 'arr2')
    {
      (this.state.arr2itemsToShow === 3) ? 
      (this.setState({ arr2itemsToShow: this.state.arr2.length, arr2ExpandFlag: true })):
      (this.setState({ arr2itemsToShow: 3, arr2ExpandFlag: false}))
    }
  }

 render()    // This is just for two cards
 {
   return
   (
      <div> 
        <div>
          {this.state.arr1.slice(0, this.state.arr1itemsToShow).map((data : any, i) => 
            <p key={i}>{data.user}</p>
          )}
          <a onClick={() => this.showMore('arr1')}>
            { (this.state.arr1ExpandFlag) ? 
              ( <span>Show less</span>)   : 
              ( <span>Show more</span>)
            }
          </a>  
        </div>
       <div>
         {this.state.arr2.slice(0, this.state.arr2itemsToShow).map((data : any, i) => 
             <p key={i}>{data.user}</p>
         )}
         <a onClick={() => this.showMore('arr2')}>
          { (this.state.arr2ExpandFlag) ? 
            (<span>Show less</span>)   : 
            (<span>Show more</span>)
          }
         </a>  
      </div>
    </div>
   )
 }

Solution

  • As others have said - build it out into reusable components. Start by identifying places where you're repeating yourself and extracting those out. It becomes easier with practice.

    I would start with Card. You can use a card type (you could also use an interface but this is personal preference) and then add an onClickExpand handler to form the Props for this control.

    type Item = {
      user: string;
    }
    
    type Card = {
      items: Item[];
      expanded?: boolean;
      numItemsShowing: number;
    }
    
    type CardProps = Card & {
      onClickExpand: () => void;
    };
    
    const Card = (props: CardProps) => {
      const { expanded, items, numItemsShowing, onClickExpand } = props;
      return (
        <div>
          {items.slice(0, numItemsShowing).map((data, i) => (
            <p key={i}>{data.user}</p>
          ))}
          <a onClick={onClickExpand}>
            {expanded
              ? <span>Show less</span>
              : <span>Show more</span>
            }
          </a>
        </div>
      );
    }
    

    Then your app can be simpler:

    type State = {
      card: Card[];
    }
    
    export default class App extends React.Component<{}, State> {
      state = { cards: [] };
    
      showMore = (index: number) => () => {
        this.setState({
          cards: this.state.cards.map((card, i) => {
            // Only modify card if it's the index we care about
            if (i === index) {
              return {
                ...card,
                expanded: true,
              }
            }
            return card;
          })
        });
      }
    
      addCard = () => {
        const newCard = {
          items: [],
          expanded: false,
          numItemsShowing: 0
        };
    
        this.setState({
          cards: [ ...this.state.cards, newCard ] // add to end of array
        });
      }
    
      render() {
        return (
          <div>
            {this.state.cards.map((card, i) =>
              <Card
                {...card}
                onClickExpand={this.showMore(i)}
                key={i}
              />
            )}
    
            <button onClick={this.addCard}>Add Card</button>
          </div>
        )
      }
    }
    

    This app lets you click the button to add a new card into the state.