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>
</>
);
}
}
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>
</>
);
}
}