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

  1. 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')
  2. Ensure the payload includes non-blank name, valid latitude/longitude, and parseable started_at/ended_at before submitting
  3. Verify this.apiKeyValue is a valid API key and the csrf-token meta tag exists on the page
  4. Disable the submit button while the request is in flight to avoid duplicate-visit RecordNotUnique
  5. 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

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.