React hooks for Jamwidgets (forms, comments, reactions, subscriptions)
npm install @jamwidgets/react> Note: This repo is a read-only mirror. Source lives in a private monorepo.
> For issues/PRs, please open them here and we'll sync changes back.
React hooks for JamWidgets widgets - comments, reactions, forms, subscriptions, and more.
``bash`
npm install @jamwidgets/react
Works with React 18+ and React 19. Compatible with Next.js, Remix, Vite, and more.
Email subscription form:
`tsx
import { useSubscribe } from "@jamwidgets/react";
function SubscribeForm() {
const [email, setEmail] = useState("");
const { submit, status, message, error } = useSubscribe({
siteKey: "your-key",
});
const handleSubmit = async (e) => {
e.preventDefault();
await submit(email);
};
return (
$3
Reaction buttons (like, love, clap, etc.):
`tsx
import { useReactions } from "@jamwidgets/react";function LikeButton() {
const { counts, userReactions, add, remove, status } = useReactions({
siteKey: "your-key",
pageId: "my-page",
});
const hasLiked = userReactions.includes("like");
return (
);
}
`$3
Threaded comments:
`tsx
import { useComments } from "@jamwidgets/react";function Comments() {
const { comments, post, status, error } = useComments({
siteKey: "your-key",
pageId: "my-page",
});
const handleSubmit = async (name, content) => {
await post(name, content);
};
return (
{comments.map((comment) => (
{comment.authorName}
{comment.content}
))}
);
}
`$3
Contact forms with spam protection:
`tsx
import { useForm } from "@jamwidgets/react";function ContactForm() {
const { submit, status, message } = useForm({
siteKey: "your-key",
formSlug: "contact",
});
const handleSubmit = async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
await submit(data);
};
return (
);
}
`$3
Waitlist signups:
`tsx
import { useWaitlist } from "@jamwidgets/react";function WaitlistForm() {
const { join, status, message, position } = useWaitlist({
siteKey: "your-key",
});
const handleSubmit = async (e) => {
e.preventDefault();
await join(email, { name, source: "homepage" });
};
return (
);
}
`$3
Feedback forms:
`tsx
import { useFeedback } from "@jamwidgets/react";function FeedbackWidget() {
const { submit, status, message } = useFeedback({
siteKey: "your-key",
});
const handleSubmit = async (type, content) => {
await submit(type, content, { email, pageUrl: window.location.href });
};
// ...
}
`$3
Polls and voting:
`tsx
import { usePoll } from "@jamwidgets/react";function Poll() {
const { poll, vote, hasVoted, status } = usePoll({
siteKey: "your-key",
pollId: 123,
});
if (!poll) return
Loading...; return (
{poll.question}
{poll.options.map((option) => (
key={option.id}
onClick={() => vote([option.id])}
disabled={hasVoted}
>
{option.text} ({poll.results?.[option.id] || 0})
))}
);
}
`$3
Site announcements:
`tsx
import { useAnnouncements } from "@jamwidgets/react";function AnnouncementBanner() {
const { announcements, dismiss, status } = useAnnouncements({
siteKey: "your-key",
});
const visible = announcements.filter((a) => !a.dismissed);
return (
<>
{visible.map((announcement) => (
{announcement.content}
{announcement.isDismissible && (
)}
))}
>
);
}
`$3
Page view tracking:
`tsx
import { useViewCounts } from "@jamwidgets/react";
import { useEffect } from "react";function PageViews() {
const { views, uniqueVisitors, record, status } = useViewCounts({
siteKey: "your-key",
pageId: "my-page",
});
// Record view on mount
useEffect(() => {
record();
}, []);
return (
{views} views ({uniqueVisitors} unique)
);
}
`All Hooks
| Hook | Purpose |
|------|---------|
|
useSubscribe | Email subscriptions |
| useReactions | Page reactions |
| useComments | Threaded comments |
| useForm | Form submissions |
| useWaitlist | Waitlist signups |
| useFeedback | Feedback forms |
| usePoll | Polls and voting |
| useAnnouncements | Site announcements |
| useViewCounts` | Page view tracking |MIT