I often find myself doing things like this when working with Reagent apps.
(defn create-account [user-details error-ref]
(let [validation-errors (do-validations user-details)]
(if validation-errors
(reset! error-ref validation-errors)
(finish-create))))
(defn component []
(let [user-details (atom {:email "" :password ""})
ui-errors (atom nil)]
(fn []
[:div.Page
(if @ui-errors
[:p @ui-errors])
[:input {:type "text" :placeholder "EMAIL" :on-change #(swap! user-details conj {:email (-> % .-target .-value)})}]
[:input {:type "password" :placeholder "PASSWORD" :on-change #(swap! user-details conj {:password (-> % .-target .-value)})}]
[:button.bottomMargin {:on-click #(create-account @user-details ui-errors)} "Submit"]])))
Where I'll set up an atom that handles storing any errors that happen and pass this into my signup flow. This is pretty simple, as a I can update the atom with any errors and show them to the user.
My concern is that this doesn't feel quite functional. I know I have this atom being updated in my block, and that feels like a side effect that ruins the purity of the function. I also dislike the need to pass the atom into my function for the sole purpose of being updated in an error state.
I suppose I will have to have side effect that at some point in my code if I want to show an error to my users, but I wanted to see if there were any better or accept patterns for doing so. I feel like my implantation works, but isn't quite as functional as I like and could potentially be hard to maintain if the flow for processing or validating the data grew.