# React feedback components > Let people file bug reports from your React app and follow the answer inside it, with unstyled components and headless hooks. `@myna-sh/react/feedback` is the reporter's side of [Myna Feedback](/concepts/reports): a form built from the board's own questions, the list of what somebody has filed, and the thread where they read the answer and say whether it worked. It runs inside **your** product, which is the point. Myna serves no reporter-facing page: your user is already signed in with you, and Myna's own identity model is GitHub OAuth — not a thing to ask of somebody who reported one bug. ```bash $ npm install @myna-sh/react @myna-sh/sdk ``` React and `@myna-sh/sdk` are peer dependencies; the package has no others. ## The whole thing ```tsx import { FeedbackProvider, ReportForm, MyReports, ReportThread, } from "@myna-sh/react/feedback"; import "@myna-sh/react/feedback.css"; // optional export function Support({ user, signature }) { const [open, setOpen] = useState(); return ( ({ appVersion: BUILD.version, route: location.pathname })} > setOpen(r.number)} /> {open ? : null} ); } ``` `board` is only needed when the ingest key is not bound to one. `context` is whatever your application knows and chooses to send; Myna never collects it itself. ## Identity `signature` comes from your own backend: ```ts import { signIdentity } from "@myna-sh/sdk/feedback/server"; // Your own API route, for the user this request is already authenticated as. export async function GET(request) { const user = await authenticate(request); return Response.json({ signature: signIdentity(process.env.MYNA_IDENTITY_SECRET, user.id), }); } ``` Without it a report can still be filed — unless the board sets `requireIdentity` — but **nothing can be read back**. An unsigned claim about who somebody is would let any visitor read another person's reports. Pass `identify` once you know who is using the application, and drop it on sign-out. The provider pushes the change into the existing client rather than rebuilding it, so signing in mid-session works without a remount; clearing it matters, because otherwise the next person on a shared machine reads the last one's reports. See [identifying your users](/concepts/collecting-feedback#identifying-your-users) for `properties`, the ceilings, and how the secret is minted and rotated. ## Components ### `ReportForm` Renders the board's intake form — every question, in the order the board asks them, including the ones that fill the report's own title and description. Nothing is hardcoded, which is what stops this component and a hand-written form from asking the same board two different things. ```tsx console.log("filed", result.number)} success={(result) =>

Thanks — that is #{result.number}.

} labels={{ submit: "Send it" }} /> ``` The form arrives one round trip late and renders a status line until it does; a form that has to be corrected after it appears is worse than one that appears a moment later. `labels` covers only what the board does not declare — the button, the confirmation, the loading and failure lines. Reword the questions in board settings. Validation errors bind back per field: the API answers with a `fields` record keyed by path and the form renders each message under the input it belongs to. ### `MyReports` Everything this identity has filed, newest activity first. ```tsx Nothing reported yet.

} children={(r) => <>{r.title} — {r.status}} /> ``` Without a signed identity it renders the empty state rather than an error, because "you have not reported anything" is what a signed-out visitor should see, not a sentence about HMAC. ### `ReportThread` One report and its conversation, with the two buttons the product exists for. ```tsx ``` "It works now" and "Still broken" appear only while the team is waiting on this person — `awaitingYou`, which is the report sitting in `needs_retest`. Showing them all the time would invite somebody to close a report nobody has looked at yet, and the confirmation would then mean nothing. What the thread shows is deliberately thin: the title, the body, the status, and the **public** conversation with the team attributed as "team" rather than by name. No priority, no labels, no assignee, no internal notes, no attachment manifest, no board guidance. ## Hooks Every component is built on these, and they are exported for anyone who would rather render their own. | Hook | Returns | |---|---| | `useFeedback()` | The underlying `FeedbackClient` from `@myna-sh/sdk/feedback` | | `useBoardSchema(board?)` | `{ data, error, isLoading, refresh }` — the board's questions | | `useSubmitReport()` | `{ submit, isSubmitting, error, result, reset }` | | `useMyReports()` | `{ data, error, isLoading, refresh }` — their reports | | `useReport(number)` | The above, plus `reply`, `confirm`, `reopen`, `isActing` | ```tsx const { data: schema } = useBoardSchema(); const { submit, isSubmitting, error } = useSubmitReport(); ``` There is no shared cache here, unlike the content hooks. A page renders dozens of content reads and one feedback surface; a cache would mostly add a way for somebody to see a stale answer to the question they just asked. Each action refetches, so the pane repaints from the server's answer rather than from a guess about what the action did. ## Styling Every element carries a `data-myna` attribute and nothing else — no class names, no inline styles, no CSS-in-JS. ```css [data-myna="submit"] { background: var(--brand); } [data-myna="field"][data-myna-field="build"] { grid-column: span 2; } [data-myna="event"][data-myna-author="you"] { text-align: right; } ``` That is not minimalism for its own sake. These components sit inside somebody else's product, next to their buttons and their type; a component that arrives with opinions is a component that has to be fought, and every widget that ships a stylesheet ends up in a specificity war with the application embedding it. The optional stylesheet is a starting point, driven by three custom properties: ```css @import "@myna-sh/react/feedback.css"; .support { --myna-accent: #e2653a; --myna-radius: 10px; --myna-font: "Inter", sans-serif; } ``` Everything else comes from `currentColor` and `color-mix`, so the form inherits the surrounding light or dark surface instead of asserting one. ## Errors `FeedbackError` from `@myna-sh/sdk/feedback` is re-exported here. It carries `status`, a stable `code`, and a `fields` record keyed by path. | Code | Meaning | |---|---| | [`IDENTITY_REQUIRED`](/errors/identity-required) | The board requires a signed identity, or a read-back call carried none | | `PERMISSION_DENIED` | The `Origin` is not registered on the project, or the key may not file on that board | | [`PRODUCT_NOT_ENABLED`](/errors/product-not-enabled) | The project does not run Feedback | | `RATE_LIMITED` | Too many submissions from this address | | `VALIDATION_FAILED` | A field ceiling or the board's intake form |