I am trying to associate a like button with each PaperCard component as shown in the code below. I have included the relevant code. Currently, The like button shows up and every time you click it the counter increases BUT all the buttons share the same state. So I am trying to fix that. I am new to JS and React.
Any help will be appreciated. Thanks!
function Home() {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes)
}
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);}
return (
<div>
<div style={{display:'flex', justifyContent:'center'}}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</Grid>
))}
<div style={{display:'flex', justifyContent: 'center'}}>
<Button variant="contained" onClick={loadMorePapers}>Load More</Button>
</div>
</div>
)
}
The element from the map callback is extracted as a component, and now every button has its own state.
function Home() {
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<LikeButton paper={paper} key={paper.title} />
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<button variant="contained" onClick={loadMorePapers}>Load More</button>
</div>
</div>
);
}
function LikeButton(paper) {
const [likes, setLikes] = useState(0);
const incrementLikes = () => {
const addToLikes = likes + 1;
setLikes(addToLikes);
};
return (
<div key={paper.title}>
<button onClick={incrementLikes}>Likes: {likes}</button>
<PaperCard title={paper.title} abstract={paper.abstract}/>
</div>
);
}
Create a new functional component called LikeButton (or something relevant) to house the state for each button independently.
In that component, add the state values you want to track per each button. In your case it seems to just be the likes.
So could be something like:
const LikeButton = () => {
const [likes, setLikes] = useState(0); //likes controlled by state of component
const incrementLikes = () => {
setLikes((prevState) => prevState + 1);
};
return <button onClick={incrementLikes}>Likes: {likes}</button>;
};
Then add that component in place of your existing button and remove the state for likes in the Home component. E.g.:
function Home() {
const loadMorePapers = () => {
setVisible((prevValue) => prevValue + 3);
};
return (
<div>
<div style={{ display: "flex", justifyContent: "center" }}>
<h1>Latest Papers</h1>
</div>
{apiData.slice(0, visible).map((paper) => (
<Grid key={paper.title}>
<LikeButton/>
<PaperCard title={paper.title} abstract={paper.abstract} />
</Grid>
))}
<div style={{ display: "flex", justifyContent: "center" }}>
<Button variant="contained" onClick={loadMorePapers}>
Load More
</Button>
</div>
</div>
);
}
Should you want to control state from the Home component, you can pass the likes as props, but it doesn't seem necessary for what you want.
In this situation you should consider using a reusable button component in order to control state within the component itself. Then you do not have to worry about the buttons sharing the same state. Here would be a simple example of a button component that will track it's count independent of the other buttons that are rendered:
import React, { useState } from 'react';
export default function CounterButton() {
const [count, setCount] = useState(0);
function incrementLikes() {
setCount(count + 1);
}
return (
<button onClick={incrementLikes}>
{count} likes
</button>
);
}
You could simply render these buttons like in the pseudo code below:
{[0, 1, 2, 3].map((num: number, index: number) => (
<div key={index}>
<CounterButton />
</div>
))}