0

i'm mapping a reviews API's array and i want to show only the clicked review when i click on "read more" but at the moment is expanding all the reviews of my array, i'm using typescript and it's all new to me so i don't know how to move, how should i pass the information of the index to my state?

interface State {

  reviews: Review[];
  isReadMore: boolean;
}
export default class MoviePage extends Component<{}, State> {
  state: State = {
    reviews: [],
    isReadMore: false,
  };
  componentDidMount() {
    this.asyncAwaitFunc();
    this.toggle(arguments);
  }
      asyncAwaitFunc = async () => {
        try {
          
          const reviewmovie = await axios.get<ReviewResponse>(
            `https://api.themoviedb.org/3/movie/${this.props.match.params.id}/reviews?api_key=`
          );
          this.setState({
    
            reviews: reviewmovie.data.results,
          });
        } catch (error) {}
      };


  toggle(index: any) {
    this.setState({
      isReadMore: !this.state.isReadMore,
    });


    
      render() {
        const { isReadMore, reviews } = this.state;

    
    
        return (
          <>
            
            <ReviewGrid>
              {reviews.map((review, index) => (
               
                   <ReviewContent key={index}>
                    {this.state.isReadMore
                      ? review.content.substring(0, 650)
                      : review.content}

                    <Button onClick={() => this.toggle(index)}>
                      {isReadMore ? "...read more" : " show less"}
                    </Button>
                  </ReviewContent>
                   
              ))}
            </ReviewGrid>
          </>
        );
    
      }
    }
2
  • Do you want only one review to be expanded at a time, or any as long as they've been clicked? Commented Aug 11, 2021 at 20:05
  • Any as long as they've been clicked! Commented Aug 11, 2021 at 22:53

2 Answers 2

1

I think that the problem is that you save isReadMore once but you need to save isReadMore for each review. Here is an example:

interface ReviewRow {
  review: Review
  isReadMore: boolean
}
interface State {
  reviews: ReviewRow[]
}
export default class MoviePage extends Component<{}, State> {
  state: State = {
    reviews: []
  }

  componentDidMount() {
    this.asyncAwaitFunc()
  }

  asyncAwaitFunc = async () => {
    try {
      const reviewMovies = await axios.get<ReviewResponse>(
        `https://api.themoviedb.org/3/movie/${this.props.match.params.id}/reviews?api_key=`
      )
      this.setState({
        reviews: reviewMovies.data.results.map((review) => {
          return { review: review, isReadMore: false }
        })
      })
    } catch (error) {
      console.log(error)
    }
  }

  toggle(index: number) {
    const { reviews } = this.state
    reviews[index].isReadMore = !reviews[index].isReadMore
    this.setState({ reviews })
  }

  render() {
    const { reviews } = this.state
    return (
      <>
        <ReviewGrid>
          {reviews.map((reviewRow, index) => {
            ;<ReviewContent key={index}>
              { reviewRow.isReadMore ? reviewRow.review.content.substring(0, 650) : reviewRow.review..content}
              <Button onClick={() => this.toggle(index)}>{reviewRow.isReadMore ? '...read more' : ' show less'}</Button>
            </ReviewContent>
          })}
        </ReviewGrid>
      </>
    )
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

With your answer, I believe this review.isReadMore ? review.content.substring(0, 650) : review.content should be review.isReadMore ? review.review.content.substring(0, 650) : review.review.content
@no_stack_dub_sack you are right. I edited the answer. Thanks
0

I've modified your code a bit to make this work since I don't have access to the API or the various interfaces and components you're using, but this should give you an idea. You're tracking isReadMore as a single piece of state, which is why it's toggling for every item in the array. You need to track state individually for each review. This is one of several solutions that could work, but the idea here is to take the API's response, and map it to a new set of objects which includes a new key that you'll add, isReadMore, then you can toggle this property individually for each review.

Here is the working example on CodeSandbox.

EDIT: Here is a link to a second example which does not require you to map over the results of the api call to add a new key to track isReadMore state. This approach tracks the state separately in a Map<Review, boolean> instead. An ES6 map works well here because you can use the review object itself as the key and the boolean value can track your hide/show state.


Original Solution

interface Review {
    title: string;
    content: string;
}

interface MyReview extends Review {
    isReadMore: boolean;
}

interface State {
    reviews: MyReview[];
}

export default class MoviePage extends React.Component<{}, State> {
    state: State = {
        reviews: []
    };

    componentDidMount() {
        this.asyncAwaitFunc();
    }

    asyncAwaitFunc = () => {
        try {
            const reviewsFromApi: Review[] = [
                {
                    title: "Some Movie",
                    content: "some movie review that's pretty long"
                },
                {
                    title: "Some Other Movie",
                    content: "an even longer super long movie review that's super long"
                }
            ];

            this.setState({
                reviews: reviewsFromApi.map((r) => ({ ...r, isReadMore: false }))
            });
        } catch (error) {
            console.log(error);
        }
    };

    toggle(index: number) {
        this.setState({
            reviews: this.state.reviews.map((r, i) => {
                if (i === index) {
                    return {
                        ...r,
                        isReadMore: !r.isReadMore
                    };
                }

                return r;
            })
        });
    }

    render() {
        const { reviews } = this.state;

        return (
            <>
                <div>
                    {reviews.map((review, index) => (
                        <div key={index}>
                            <p>
                                {review.isReadMore
                                    ? review.content.substring(0, 10) + "..."
                                    : review.content}
                            </p>

                            <button onClick={() => this.toggle(index)}>
                                {review.isReadMore ? "Read more" : " Show less"}
                            </button>
                        </div>
                    ))}
                </div>
            </>
        );
    }
}

2 Comments

Thanks for taking the time to explain your solution! i really appreciate
Glad you found it helpful. Good luck!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.