react-simple-votes
v1.0.0
Published
A simple React hook for like/dislike voting with localStorage fallback and API support
Maintainers
Readme
react-simple-votes
A simple React hook for like/dislike voting. Works out of the box with localStorage, or connect your own API.
✨ Features
- 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
📦 Installation
npm install react-simple-votes🚀 Quick Start
import { 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>
);
}🔌 Connect Your API
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',
});Expected API Format
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 }⚙️ Options
useVotes({
fetchUrl?: string; // GET endpoint for fetching votes
submitUrl?: string; // POST endpoint for submitting votes
storageKey?: string; // localStorage key prefix (default: 'react-simple-votes')
});📋 API Reference
useVotes(options?)
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 |
Types
type VoteType = 'like' | 'dislike';
interface VoteData {
likes: number;
dislikes: number;
}
interface VotesMap {
[itemId: string]: VoteData;
}
interface UserVotesMap {
[itemId: string]: VoteType;
}🎨 Example: Styled Buttons
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>
);
}📄 License
MIT License - feel free to use in your own projects!
