React Native
The FeedbackJar React Native SDK is a data layer plus an optional prebuilt board. Use just the data layer if you’re building your own UI, or drop in FeedbackJarBoard for a complete list → detail → new feedback flow with voting and comments already wired up.
- Package:
@feedbackjar/react-native-sdk(0.7.1) - Min React Native: 0.70.0
- Platforms: iOS & Android
- Repository: github.com/feedbackjar/react-native-sdk
Installation
npm install @feedbackjar/react-native-sdk@0.7.1 The SDK ships two small autolinked native helpers (app id + on-device storage). No manual linking. Networking uses React Native’s built-in fetch, Platform, and Dimensions APIs.
Expo Go: custom native modules don’t load in Expo Go. Install the optional
@react-native-async-storage/async-storageas a fallback, or use a dev/bare build.
Setup
Configure once before use, typically in your root App.tsx:
import { FeedbackJar } from '@feedbackjar/react-native-sdk';
FeedbackJar.configure({ widgetId: 'your-widget-id' }); Get your widget ID from Settings → Widgets in your FeedbackJar project.
Prebuilt board (optional)
Skip building your own screens — FeedbackJarBoard is a complete board built only on React Native primitives (no extra UI dependency):
import { FeedbackJarBoard } from '@feedbackjar/react-native-sdk';
function FeedbackScreen() {
return <FeedbackJarBoard accentColor="#e5484d" />;
} | Prop | Description |
|---|---|
accentColor? | Colour for the vote state, primary button and links. Default #e5484d. |
boardId? | Restrict the feed to one board. |
onClose? | When set, a “Close” action appears in the header. |
Gives you list, upvote, detail, comments, and a new-feedback form. Nothing throws; failures render inline. Follows system light/dark.
Identify the submitter (optional)
Remember a name/email so later submit, vote, and addComment calls attribute to the same person, and so their name/email are synced to the server against this device’s anonymous id:
await FeedbackJar.setIdentity({ name: user.name, email: user.email });
const identity = await FeedbackJar.getIdentity();
// On logout:
await FeedbackJar.clearIdentity(); Skip this and submissions stay fully anonymous — setIdentity is optional, not required.
Verified identity (signed-in users)
If your app has its own signed-in users, attribute their votes/comments to a real, verifiable account — consistent across every device they use — instead of the unverified name/email above. Your backend computes the signature:
// Your backend, once the user is authenticated:
// timestamp = Date.now()
// signature = HMAC-SHA256(orgSecretKey, `${userId}:${email}:${timestamp}`)
// (org secret key is in the FeedbackJar dashboard)
await FeedbackJar.setIdentity({
userId: user.id,
email: user.email,
signature: signatureFromYourBackend,
timestamp: timestampFromYourBackend,
}); Once set, vote, addComment, submit, and hasVoted all attach to this real user on every device — immediately, and any votes/comments already made anonymously on this device are folded in automatically. timestamp must be the exact value your backend signed, and expires after 7 days — call setIdentity again on each sign-in.
Submit feedback
Each submission automatically includes device metadata (OS version, screen size, locale, timezone).
const result = await FeedbackJar.submit(userText);
if (result.ok) {
console.log('Submitted:', result.value.postId);
} else {
console.error('Failed:', result.error.message);
} A callback variant is available when you are not in an async context:
FeedbackJar.submit(userText, undefined, (result) => {
if (result.ok) {
// show success state
}
}); Pass email/name for a headless submission where identity isn’t already known, and properties for your own key/value context merged into the auto-collected app metadata:
await FeedbackJar.submit(userText, { properties: { plan: 'pro' } }); List feedback
Fetch the public feedback feed with optional pagination:
const result = await FeedbackJar.listFeedback({ limit: 20 });
if (result.ok) {
for (const post of result.value.posts) {
console.log(`${post.title} — ${post.upvotes} upvotes`);
}
} Pass boardId to filter by board, or cursor for pagination.
FeedbackJar.getPost(postId) fetches a single public post the same way.
Voting
Guest upvoting as this device’s anonymous guest — idempotent, and gated by the project’s Allow guest votes setting (WidgetConfig.allowVotes, see Get widget config):
await FeedbackJar.vote(postId); // upvote
await FeedbackJar.unvote(postId); // remove the upvote
const state = await FeedbackJar.getVoteState(postId); // current count + hasVoted Comments
Anonymous, two-level threads — gated by Allow guest comments (WidgetConfig.allowComments):
const comments = await FeedbackJar.listComments(postId, { limit: 20 });
// Reply to a post, or to a root comment via parentId (replies-to-replies aren't allowed)
await FeedbackJar.addComment(postId, 'Same issue here!'); name/email fall back to the remembered identity (setIdentity) when omitted.
Get widget config
Check whether the org asks for name/email, and whether voting/comments are enabled, before building your own UI around them:
const result = await FeedbackJar.getConfig();
if (result.ok) {
if (result.value.collectEmail) { /* show an email field */ }
if (result.value.allowVotes) { /* show your vote button */ }
} | Field | Meaning |
|---|---|
collectName | Org asks submitters for their name (“Ask for Name”) |
collectEmail | Org asks submitters for their email (“Ask for Email”) |
allowVotes | Guest upvoting is enabled for this project |
allowComments | Guest commenting is enabled for this project |
API reference
| Method | Description |
|---|---|
configure({ widgetId }) | Configure the SDK. Call once before anything else. |
setIdentity({ name?, email? }) | Remember a submitter’s name/email for later calls. |
setIdentity({ userId, email, signature, timestamp, ... }) | Set a verified, org-signed identity — see Verified identity. |
getIdentity() | The currently remembered identity, if any. |
clearIdentity() | Forget the remembered identity (e.g. on logout). |
submit(content, options?) | Submit feedback. options: email?, name?, properties?. Returns FeedbackJarResult<FeedbackResponse>. |
listFeedback(options?) | List public feedback. options: boardId?, limit?, cursor?. limit is clamped to 1–50. |
getPost(postId) | Fetch a single public post. |
getConfig() | Fetch this project’s WidgetConfig. |
vote(postId) / unvote(postId) | Cast or remove this device’s upvote. Idempotent. |
getVoteState(postId) | Current upvote count and whether this device voted. |
listComments(postId, options?) | List a post’s comments (two levels). options: limit?, cursor?. |
addComment(postId, content, options?) | Add a comment or reply. options: parentId?, name?, email?. |
submit and listFeedback also accept a trailing callback for use outside an async context. All methods return a discriminated union — nothing throws on network errors:
type FeedbackJarResult<T> =
| { ok: true; value: T }
| { ok: false; error: Error }; Notes
- Submission, voting, and comments are all anonymous by default —
setIdentityattaches a name/email, it does not require a full account or portal login. - Voting and commenting only work when the project has them enabled — check
getConfig()before showing that UI. - The server rate-limits submissions (5 per 15 minutes per IP).
- Private boards and non-public posts are never returned by
listFeedback.