Feedback
Send bugs, questions, ideas, and other messages from your users with device context and attachments.
Feedback is any message a user sends you: a bug, a question, an idea, or
anything else. Send it with mite.feedback.send() or the useFeedback() hook,
or mount ShakeToReport and let the SDK run the whole
bug flow.
Only message is required. The SDK adds device info, the current identity, the
build, and the navigation trail to every message.
await mite.feedback.send({ type: 'question', message: 'How do I export my data?' })Feedback types
type tells your dashboard what kind of message it is. It defaults to 'other'.
type | Use it for | Bug-only fields allowed |
|---|---|---|
'bug' | Something is broken | Yes: steps, expected, actual |
'question' | The user needs help | No |
'idea' | A suggestion for you | No |
'other' | Anything else. The default | No |
feedback.send does not post to the public feature board. To post a request
there, where other users vote on it, use
mite.features.request().
steps, expected, and actual exist only on type: 'bug'. The input type is a
discriminated union, so TypeScript rejects them on any other type:
await mite.feedback.send({
type: 'bug',
message: 'Checkout button does nothing',
steps: '1. Add an item\n2. Tap checkout',
expected: 'The payment sheet opens',
actual: 'Nothing happens',
})
await mite.feedback.send({
type: 'question',
message: 'Can I pay yearly?',
steps: '...', // Type error: steps is only allowed when type is 'bug'
})Using the hook
useFeedback() wraps mite.feedback.send() with its submission state.
import { useFeedback } from '@usemite/sdk'
import { useState } from 'react'
export default function FeedbackScreen() {
const { send, sending, error, refusal } = useFeedback()
const [message, setMessage] = useState('')
const handleSend = async () => {
const result = await send({ type: 'question', message })
if (result.ok) setMessage('')
}
return <Button onPress={handleSend} disabled={sending} title="Send" />
}send rejects when the request fails, and also sets error. Wrap the call in
try/catch if you handle the failure inline.
A plan quota refusal does not reject. It is an expected state, not
a fault. It comes back as ok: false in the result and on refusal.
Return value
| Field | Type | Description |
|---|---|---|
send | (input: SendFeedbackInput) => Promise<SendFeedbackResult> | Send feedback |
sending | boolean | Whether a request is in flight |
error | Error | null | Last request error |
lastResponse | SubmitBugReportResponse | null | Response from the last successful send |
refusal | MiteQuotaRefusal | null | Set when the account is over a plan limit |
reset | () => void | Clear error, lastResponse, and refusal |
Input fields
type FeedbackType = 'bug' | 'question' | 'idea' | 'other'
type SendFeedbackInput = {
message: string
title?: string
reporter?: { name?: string; email?: string }
screenshot?: string
attachments?: MiteAttachment[]
context?: Record<string, string>
} & (
| { type: 'bug'; steps?: string; expected?: string; actual?: string }
| { type?: 'question' | 'idea' | 'other' }
)| Field | Description |
|---|---|
message | What the user wrote. Required |
type | One of the feedback types. Defaults to 'other' |
title | Shown in the dashboard. The server uses the message's first line when you omit it |
reporter | Name and email the user typed in. Defaults to the identified user |
screenshot | Local URI of a screenshot. Shorthand for the first attachment |
attachments | Files to upload. See Attachments |
context | Flat string keys of your own, such as a feature flag or an experiment bucket |
steps, expected, actual | Steps to reproduce, expected result, and actual result. type: 'bug' only |
There is no priority field. Priority is set during triage in the Mite
dashboard, not by the app.
The result
feedback.send resolves to the same result as the 1.0 submitBug:
type SendFeedbackResult =
| {
ok: true
report: SubmitBugReportResponse
/** Set when the attachments did not fit in the storage quota. */
droppedAttachments?: { count: number; refusal: MiteQuotaRefusal }
}
| { ok: false; refusal: MiteQuotaRefusal }
interface SubmitBugReportResponse {
id: string
status: 'NEEDS_TRIAGE'
}New feedback always lands as NEEDS_TRIAGE. ok: false means the account is
over a plan limit and Mite created nothing.
const result = await mite.feedback.send({ type: 'bug', message })
if (result.ok) {
console.log(result.report.id)
}What the SDK fills in automatically
You do not set these yourself:
- Identity. The current anonymous ID, and the identified user when there is one.
- Device info. Brand, model, OS name and version, memory, and CPU
architectures, read with
expo-device. - Build.
app_versionfromexpo-applicationorexpo-constants.eas_update_id,channel, andruntime_versionfromexpo-updateswhen it is installed. Mite files the message under the release the user was running. - Triage context.
current_route,last_error_message,last_error_stack, andnetwork_statewhen the SDK knows them.network_stateneeds the optional@react-native-community/netinfopeer dependency. Yourcontextkeys win over these. - Navigation trail. The last screens visited, when breadcrumbs are on.
In anonymous-only mode the SDK sends the anonymous
ID only. It leaves out the user ID, reporter, and device info.
Attachments
Pass local file URIs. The SDK requests an upload URL, uploads each file, and sends the message with references to the stored files.
await mite.feedback.send({
type: 'bug',
message: 'The avatar overlaps the username at small font scales.',
screenshot: screenshotUri,
attachments: [{ uri: logFileUri, type: 'text/plain', name: 'log.txt' }],
})screenshot becomes the first attachment. uri is anything fetch() can read:
an expo-image-picker result, a react-native-view-shot capture, or a
file:// path. type defaults to the file's own MIME type, then image/jpeg.
Attachments cannot be uploaded offline. When the
offline queue holds a message, the SDK drops its
attachments and logs a [Mite] warning. The message itself is still sent on
retry.
Error handling
feedback.send throws when the API key is missing, when the server rejects the
request, or when the network fails. On a network error with the offline queue
on, the SDK queues the message and still throws, so your UI decides what to
show:
try {
await mite.feedback.send({ type: 'bug', message })
showToast('Thanks for the report!')
} catch {
showToast(
mite.pendingRequestCount > 0
? "Saved. We'll send it when you're back online."
: 'Could not send your report.',
)
}Server requirement
feedback.send posts to /api/v1/feedback, so it needs a Mite server that has
that route. If you point endpoint at your own server, update the server first.
The deprecated submitBug keeps posting to /api/v1/bug-reports.
Moving from submitBug
submitBug and useBugReport still work. submitBug is deprecated and is
removed in 2.0.
| 1.0 | Now |
|---|---|
mite.submitBug({ title, description }) | mite.feedback.send({ type: 'bug', message }) |
description | message |
steps_to_reproduce, expected_behavior, actual_behavior | steps, expected, actual |
reporter_name, reporter_email | reporter: { name, email } |
environment | context |
useBugReport() → submitBug, submitting | useFeedback() → send, sending |
The result shape is the same, so code that reads result.ok, result.report,
and result.refusal does not change.