Freika/dawarich · error · Error
errorData.error || `Failed to ${isEdit ? "update" : "create"
Error message
errorData.error || `Failed to ${isEdit ? "update" : "create"} visit` What it means
Thrown by the Stimulus visit-creation controller when the POST/PATCH to the visits API returns a non-2xx response. The Rails endpoint (Api::V1::VisitsController#create/update) answers 422 Unprocessable Content with a JSON body like {error: 'Failed to create visit: ...'} when Visits::Create fails (duplicate visit, invalid place, ActiveRecord::RecordInvalid) and 401 when the Bearer key/CSRF is bad. The fallback template string 'Failed to create/update visit' only surfaces when the body is JSON but has no 'error' key.
Source
Thrown at app/javascript/controllers/visit_creation_v2_controller.js:179
const url = isEdit
? `/api/v1/visits/${this.editingVisitId}`
: "/api/v1/visits"
const method = isEdit ? "PATCH" : "POST"
const response = await fetch(url, {
method: method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKeyValue}`,
"X-CSRF-Token":
document.querySelector('meta[name="csrf-token"]')?.content || "",
},
body: JSON.stringify(visitData),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(
errorData.error || `Failed to ${isEdit ? "update" : "create"} visit`,
)
}
const visit = await response.json()
// Show success message
this.showToast(
`Visit ${isEdit ? "updated" : "created"} successfully`,
"success",
)
// Close modal
this.close()
// Dispatch event to notify map controller
const eventName = isEdit ? "visit:updated" : "visit:created"
document.dispatchEvent(View on GitHub (pinned to 97fad417c5)
Solutions
- Open the browser Network tab and read the 422 body's error field - it carries the exact server message (e.g. 'Failed to create visit: Validation failed: Name can't be blank')
- Ensure the payload includes non-blank name, valid latitude/longitude, and parseable started_at/ended_at before submitting
- Verify this.apiKeyValue is a valid API key and the csrf-token meta tag exists on the page
- Disable the submit button while the request is in flight to avoid duplicate-visit RecordNotUnique
- Guard response.json() with a content-type check so HTML error pages (401/500) surface a readable message instead of a SyntaxError
Example fix
// before
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || `Failed to ${isEdit ? "update" : "create"} visit`)
}
// after
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
errorData.error ||
`Failed to ${isEdit ? "update" : "create"} visit (HTTP ${response.status})`,
)
} Defensive patterns
Strategy: try-catch
Validate before calling
const required = ['name', 'latitude', 'longitude', 'started_at', 'ended_at']
const missing = required.filter((k) => !visitData[k] && visitData[k] !== 0)
if (missing.length) {
this.showToast(`Missing: ${missing.join(', ')}`, 'error')
return
}
if (!this.apiKeyValue) {
this.showToast('API key missing', 'error')
return
} Try / catch
try {
const response = await fetch(url, opts)
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.error || `HTTP ${response.status}`)
}
// ...
} catch (error) {
this.showToast(error.message, 'error')
} finally {
submitButton.disabled = false
} Prevention
- Disable the submit button while the request is in flight to avoid duplicate-visit 422s
- Parse error bodies with response.json().catch(() => ({})) so HTML error pages do not mask the real status
- Include response.status in fallback messages because server error text is not always present
When it happens
Trigger: POST /api/v1/visits or PATCH /api/v1/visits/:id where Visits::Create rescues ActiveRecord::RecordNotUnique ('Failed to create visit: duplicate visit'), Place.create! fails RecordInvalid (bad/blank latitude, longitude, name), started_at/ended_at unparseable so Time.zone.parse returns nil and duration computation fails, or the API key meta value is empty/expired producing a 401 whose body still parses as JSON without an 'error' field.
Common situations: Double-submitting the visit form (creates a duplicate visit within the 100m ST_DWithin match), expired or rotated api_key, CSRF meta tag missing on a cached page, empty date inputs, reverse proxy returning a JSON-ish 502 body, editing a visit whose place_id/area_id belongs to another user (invalid_place/invalid_area 422 messages).
Related errors
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/79352206b12f6b24.
Report an issue: GitHub.