Skip to main content

@reloop-sdk/react

A provider, a useSubmit() hook, and a drop-in <FeedbackWidget />.

npm install @reloop-sdk/core @reloop-sdk/react

1. Wrap your app

Provide the client once, near the root. An API key can send any submission type (bug, feedback, waitlist, question or other).

import { ReloopProvider } from "@reloop-sdk/react";

export function Root() {
return (
<ReloopProvider apiKey="rl_pub_..." endpoint="https://reloop.example.com">
<App />
</ReloopProvider>
);
}

You can also pass a pre-built client prop instead of apiKey/endpoint.

2. Use the hook

useSubmit() returns { submit, reset, status, error, isSubmitting }. status is "idle" | "submitting" | "success" | "error". Build any UI you like on top of it.

Waitlist form

import { useState } from "react";
import { useSubmit } from "@reloop-sdk/react";

export function WaitlistForm() {
const { submit, status, error } = useSubmit();
const [email, setEmail] = useState("");

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
await submit({ type: "waitlist", email });
}

if (status === "success") return <p>You're on the list! 🎉</p>;

return (
<form onSubmit={onSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<button disabled={status === "submitting"}>
{status === "submitting" ? "Joining…" : "Join waitlist"}
</button>
{status === "error" && <p role="alert">{error?.message}</p>}
</form>
);
}

Bug report with a screenshot

The screenshot field takes a base64 data URL — read a file with FileReader:

import { useState } from "react";
import { useSubmit } from "@reloop-sdk/react";

export function BugReportForm() {
const { submit, status, error, reset } = useSubmit();
const [subject, setSubject] = useState("");
const [message, setMessage] = useState("");
const [screenshot, setScreenshot] = useState<string>();

function onFile(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => setScreenshot(reader.result as string);
reader.readAsDataURL(file); // data:image/png;base64,...
}

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
await submit({
type: "bug",
subject,
message,
screenshot,
meta: { route: window.location.pathname },
});
}

if (status === "success") {
return <button onClick={reset}>Report another bug</button>;
}

return (
<form onSubmit={onSubmit}>
<input
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="Short summary"
required
/>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="What happened?"
required
/>
<input type="file" accept="image/*" onChange={onFile} />
<button disabled={status === "submitting"}>
{status === "submitting" ? "Sending…" : "Send report"}
</button>
{status === "error" && <p role="alert">{error?.message}</p>}
</form>
);
}

Feedback in one line

const { submit } = useSubmit();
<button onClick={() => submit({ type: "feedback", message: "Love it!" })}>
Send feedback
</button>;

useReloop()

Returns the underlying @reloop-sdk/core client, if you need to call submit outside React state:

import { useReloop } from "@reloop-sdk/react";

const reloop = useReloop();
await reloop.submit({ type: "feedback", message: "Nice!" });

<FeedbackWidget />

A drop-in floating launcher + panel that submits a feedback item.

import { ReloopProvider, FeedbackWidget } from "@reloop-sdk/react";

<ReloopProvider apiKey="rl_pub_..." endpoint="https://reloop.example.com">
<App />
<FeedbackWidget title="Send feedback" position="bottom-right" />
</ReloopProvider>;
PropTypeDefault
titlestring"Send feedback"
position"bottom-right" | "bottom-left""bottom-right"