A simple React hook for like/dislike voting. Works out of the box with localStorage, or connect your own API.
- Zero Config - Works immediately with localStorage
- API Ready - Just add two URLs to connect your backend
- Optimistic Updates - UI updates instantly, syncs in background
- Vote Tracking - Remembers user votes, prevents duplicates
- Vote Changing - Users can switch from like to dislike
- TypeScript - Full type support included
npm install react-simple-votesimport { useVotes } from 'react-simple-votes';
function LikeButton({ itemId }: { itemId: string }) {
const { votes, userVotes, vote } = useVotes();
const hasLiked = userVotes[itemId] === 'like';
const hasDisliked = userVotes[itemId] === 'dislike';
return (
<div>
<button
onClick={() => vote(itemId, 'like')}
disabled={hasLiked}
style={{ opacity: hasLiked ? 1 : 0.6 }}
>
👍 {votes[itemId]?.likes || 0}
</button>
<button
onClick={() => vote(itemId, 'dislike')}
disabled={hasDisliked}
style={{ opacity: hasDisliked ? 1 : 0.6 }}
>
👎 {votes[itemId]?.dislikes || 0}
</button>
</div>
);
}To persist votes across users, provide your API endpoints:
const { votes, userVotes, vote } = useVotes({
fetchUrl: 'https://api.example.com/votes',
submitUrl: 'https://api.example.com/vote',
});GET /votes - Fetch all votes
{
"item-id-1": { "likes": 42, "dislikes": 3 },
"item-id-2": { "likes": 15, "dislikes": 8 }
}POST /vote - Submit a vote
Request body:
{
"itemId": "item-id-1",
"voteType": "like",
"previousVote": "dislike"
}Response:
{ "likes": 43, "dislikes": 2 }useVotes({
fetchUrl?: string; // GET endpoint for fetching votes
submitUrl?: string; // POST endpoint for submitting votes
storageKey?: string; // localStorage key prefix (default: 'react-simple-votes')
});Returns:
| Property | Type | Description |
|---|---|---|
votes |
VotesMap |
All vote counts: { [itemId]: { likes, dislikes } } |
userVotes |
UserVotesMap |
User's votes: { [itemId]: 'like' | 'dislike' } |
vote |
(itemId, voteType) => Promise |
Submit a vote |
isLoading |
boolean |
Loading state |
error |
string | null |
Error message |
type VoteType = 'like' | 'dislike';
interface VoteData {
likes: number;
dislikes: number;
}
interface VotesMap {
[itemId: string]: VoteData;
}
interface UserVotesMap {
[itemId: string]: VoteType;
}import { useVotes } from 'react-simple-votes';
function VoteButtons({ itemId }: { itemId: string }) {
const { votes, userVotes, vote, isLoading } = useVotes();
const itemVotes = votes[itemId] || { likes: 0, dislikes: 0 };
const userVote = userVotes[itemId];
return (
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => vote(itemId, 'like')}
disabled={isLoading || userVote === 'like'}
style={{
padding: '8px 16px',
background: userVote === 'like' ? '#22c55e' : '#f0f0f0',
color: userVote === 'like' ? 'white' : 'black',
border: 'none',
borderRadius: '20px',
cursor: userVote === 'like' ? 'default' : 'pointer',
}}
>
👍 {itemVotes.likes}
</button>
<button
onClick={() => vote(itemId, 'dislike')}
disabled={isLoading || userVote === 'dislike'}
style={{
padding: '8px 16px',
background: userVote === 'dislike' ? '#ef4444' : '#f0f0f0',
color: userVote === 'dislike' ? 'white' : 'black',
border: 'none',
borderRadius: '20px',
cursor: userVote === 'dislike' ? 'default' : 'pointer',
}}
>
👎 {itemVotes.dislikes}
</button>
</div>
);
}MIT License - feel free to use in your own projects!