esc

Type to search...

The Problem

Some validations can't run on the client. "Is this email already taken?" requires a server round-trip. You need to debounce the request so you're not hitting the server on every keystroke, track whether a request is still pending, inject server errors into the form state, and prevent submission while the check is in flight. Doing all of this manually for every field that needs server validation is tedious and error-prone.

Fork's send-server-request handler wraps this entire flow. You configure it per-field, and Fork manages the timing, waiting state, error injection, and submission blocking.

Basic Server Request

Here's a complete example: an email field that checks server-side availability on every change, debounced to 500ms:

server-request.cljs
(ns app.core
  (:require
   [fork.re-frame :as fork]
   [re-frame.core :as rf]))

(rf/reg-event-fx
 :check-email
 (fn [_ [_ {:keys [values errors path state]}]]
   ;; only check if client-side validation passes
   (when (empty? (get errors "email"))
     ;; faking a server request
     {:dispatch-later
      [{:ms 1000
        :dispatch [:email-response path
                   (get values "email")]}]})))

(rf/reg-event-fx
 :email-response
 (fn [{db :db} [_ path email]]
   {:db
    (if (= "taken@example.com" email)
      (-> db
          (fork/set-error path "email" "Email already taken")
          (fork/set-waiting path "email" false))
      (fork/set-waiting path "email" false))}))

(defn email-form []
  [fork/form {:path :form
              :prevent-default? true
              :validation
              #(cond-> {}
                 (empty? (get % "email"))
                 (assoc "email" "Email is required"))
              :on-submit #(js/alert (:values %))}
   (fn [{:keys [form-id values errors server-errors
                handle-change handle-blur handle-submit
                send-server-request]}]
     [:form
      {:id form-id
       :on-submit handle-submit}
      [:input
       {:name "email"
        :value (values "email")
        :on-blur handle-blur
        :on-change
        (fn [evt]
          (handle-change evt)
          (send-server-request
           {:name "email"
            :value (fork/retrieve-event-value evt)
            :set-waiting? true
            :clean-on-refetch ["email"]
            :debounce 500}
           #(rf/dispatch [:check-email %])))}]
      [:div (or (get errors "email")
                (get server-errors "email"))]
      [:button {:type "submit"} "Submit"]])])

Config Options

The first argument to send-server-request is a config map:

Prop Type Default Description
:name string Required. The field name. Used as a key for internal state tracking (debounce timers, waiting flags).
:value any The current field value. Use fork/retrieve-event-value to extract it from the DOM event. If omitted, the callback receives the previous value.
:evt keyword Set to :on-blur to mark the field as touched when the server request fires. Relevant for blur-triggered validations.
:set-waiting? boolean Defaults to true. When true, sets a waiting flag that prevents form submission until the server responds.
:clean-on-refetch vector Field names whose server-side state (errors, waiting) should be cleared before each new request. Prevents stale errors from lingering.
:debounce number (ms) Debounce delay in milliseconds. The callback fires only after the user stops triggering events for this duration. Mutually exclusive with :throttle.
:throttle number (ms) Throttle interval in milliseconds. The callback fires at most once per interval. Mutually exclusive with :debounce.

The second argument is a callback function that performs the actual server request. It receives a map with :state, :path, :values, :touched, :errors, and :dirty.

Resolving the Waiting State

When :set-waiting? is true, Fork blocks submission until you explicitly clear the waiting flag. After your server logic resolves, call set-waiting with false:

resolve-waiting.cljs
;; In your response handler:
(rf/reg-event-fx
 :email-response
 (fn [{db :db} [_ path result]]
   {:db
    (if (:error result)
      ;; Set error AND clear waiting
      (-> db
          (fork/set-error path "email" (:message result))
          (fork/set-waiting path "email" false))
      ;; Just clear waiting (no error)
      (fork/set-waiting path "email" false))}))

Submission with Server Messages

For post-submission server responses (success or failure messages), use set-server-message:

server-messages.cljs
(rf/reg-event-fx
 :submit-success
 (fn [{db :db} [_ path result]]
   {:db (-> db
            (fork/set-submitting path false)
            (fork/set-server-message path "Saved!"))}))

(rf/reg-event-fx
 :submit-failure
 (fn [{db :db} [_ path]]
   {:db (-> db
            (fork/set-submitting path false)
            (fork/set-server-message path "Something went wrong."))}))

;; In the form component:
(fn [{:keys [on-submit-server-message ...]}]
  [:div
   ;; ... form fields ...
   (when on-submit-server-message
     [:p.message on-submit-server-message])])

Next Steps