Fork me on GitHub
#re-frame
<
2021-06-20
>
zackteo01:06:45

Hello, what should I be using/doing to get the state in the re-frame db and edit it. Like how to I get a copy of the data I want in the db so I can manipulate it locally then use and effect to remove the old and add the new

zackteo01:06:00

I saw that the todo example does have edit - but that seems to work because the local state is stored in that component

emccue02:06:02

lets say your app-db looks like this

emccue02:06:29

{:some.ns/page-state {:first-name "" :last-name ""}}

emccue02:06:47

you can write a subscription to get the current page state

emccue02:06:12

(let [page-state @(rf/subscribe [:some.ns/page-state])]
  ...)

emccue02:06:23

and then pass that to a component

emccue02:06:54

(defn info-editor [{:keys [first-name last-name]}]
  [:div
    [input-component {:value first-name}]
    [input-component {:value last-name}]])
  
(let [page-state @(rf/subscribe [:some.ns/page-state])]
  [info-editor page-state])

emccue02:06:19

and then have events that fire when a user performs an action

emccue02:06:48

(rf/reg-event-fx
  :some.ns/edited-first-name
  (fn [{:keys [db]} [_ new-first-name]]
    {:db (assoc-in db [:some.ns/page-state :first-name] new-first-name)}))

(rf/reg-event-fx
  :some.ns/edited-last-name
  (fn [{:keys [db]} [_ new-last-name]]
    {:db (assoc-in db [:some.ns/page-state :last-name] new-last-name)}))

emccue02:06:18

and dispatch those events when the user does a thing

emccue02:06:11

(defn info-editor [{:keys [first-name last-name]}]
  [:div
    [input-component {:value first-name
                      :on-change #(rf/dispatch [:some.ns/edited-first-name %])}]
    [input-component {:value last-name
                      :on-change #(rf/dispatch [:some.ns/edited-last-name %])}]])

(let [page-state @(rf/subscribe [:some.ns/page-state])]
  [info-editor page-state])

emccue02:06:35

it feels silly for events that just do an assoc, sure

emccue02:06:06

but its a good general model

emccue02:06:29

and if you need to perform side effects as part of handling an event you can include an :fx key in the map you return

zackteo02:06:42

so for say an example of a form, i should have events for each of the parameters of that form? Currently I have it saved in a local reagent atom, and only dispatch an event to submit the data when the submit button is pressed

zackteo02:06:21

This worked. But now im unsure what to do if I want to edit my form data

emccue02:06:23

i would treat that as an approach to take only when it has proven to be a performance concern

emccue02:06:39

otherwise just avoid local atoms alltogether

emccue02:06:22

and if it is just an "assoc" you can cheat a little bit and have one event that is like "update-form-field" that takes a key and a value

emccue02:06:32

thats a practical compromise

zackteo02:06:26

but what if i only want to edit the data in the re-frame db when i confirm it ?

emccue02:06:47

why would you want that?

emccue02:06:59

or actually

emccue02:06:02

nvm i see the use

emccue02:06:08

what i've done in the past is have

emccue02:06:25

{:form-state .... :editing-form-state nil}

emccue02:06:43

and when they start editing i copy over the form state and work in the editing form state

emccue02:06:02

then when they slap submit, put the editing state in form state

zackteo02:06:07

how do i do that?

emccue02:06:42

if I am interpreting your semantics correctly, you have a page where the user takes some action that puts them into "edit mode"?

emccue02:06:57

and then if they click cancel none of what they did applies

emccue02:06:04

and if they click submit they have edited the form

zackteo02:06:07

yeah something like that

emccue02:06:49

{:some.ns/page-state 
  {:form-state {:first-name "" :last-name ""}}
  {:edit-state nil}}

emccue02:06:52

start with this

emccue02:06:21

(defn editing? [page-state]
  (some? (:edit-state page-state)))

zackteo02:06:56

i think the part I want to know specifically is I have state (rf/subscribe [:access-constraint id])

(rf/reg-sub
  :access-constraint
  (fn [db [_ index]]
    (get-in db [:constraints index]))) 
How do copy the data over to the :editing-form-state

emccue02:06:38

(defn info-editor [page-state]
  (if (editing? page-state)
    (let [{:keys [first-name last-name]} (:edit-state page-state)]
      [:div
        [input-component {:value first-name
                          :on-change #(rf/dispatch [:some.ns/edited-first-name %])}]
        [input-component {:value last-name
                          :on-change #(rf/dispatch [:some.ns/edited-last-name %])}]
        [button {:on-click #(rf/dispatch [:some.ns/confirm-edits])} "Confirm"]
        [button {:on-click #(rf/dispatch [:some.ns/cancel-edits])} "Cancel"]])
    (let [form-state (:form-state page-state)]
      [:div
        [display-current-state form-state]
        [button {:on-click #(rf/dispatch [:some.ns/enter-edit-mode])}
                "Edit"])
    
(let [page-state @(rf/subscribe [:some.ns/page-state])]
  [info-editor page-state])

emccue02:06:41

(rf/reg-event-fx 
  :some.ns/enter-edit-mode
  {:db (update db :some.ns/page-state
              (fn [page-state]
                (assoc page-state :edit-state (:form-state page-state)))))
 
(rf/reg-event-fx
  :some.ns/edited-first-name
  (fn [{:keys [db]} [_ new-first-name]]
    (when (editing? (:some.ns/page-state db))
      {:db (assoc-in db [:some.ns/page-state :edit-state :first-name] new-first-name)})))

(rf/reg-event-fx
  :some.ns/edited-last-name
  (fn [{:keys [db]} [_ new-last-name]]
    (when (editing? (:some.ns/page-state db))
      {:db (assoc-in db [:some.ns/page-state :edit-state :last-name] new-last-name)})))

(rf/reg-event-fx
  :some.ns/confirm-edits
  (fn [{:keys [db]} _]
    (when (editing? (:some.ns/page-state db))
      {:db (update db :some.ns/page-state
              (fn [page-state]
                (-> page-state
                    (assoc :form-state (:edit-state page-state))
                    (assoc :edit-state nil))))

(rf/reg-event-fx
  :some.ns/cancel-edits
  (fn [{:keys [db]} _]
    (when (editing? (:some.ns/page-state db))
      {:db (update db :some.ns/page-state
              (fn [page-state]
                (-> page-state
                   (assoc page-state :edit-state nil)))))

emccue02:06:45

there we go

emccue02:06:00

the data is in your app db, you can copy it with an assoc

emccue02:06:15

you see that in :some.ns/enter-edit-mode

zackteo02:06:40

sorry maybe I should put the code into a page and run it but ... won't :value be left unchanged since the on-change only edits edited-first-name for example? Meaning to say the display won't update as you try to change say the name

emccue03:06:28

sorry tabbed out

emccue03:06:44

the on change calls edit-first-name

emccue03:06:56

the view depends on the value of :first-name in the :edit-state

emccue03:06:10

so when the event fires the view will be re-rendered with the new value

emccue03:06:31

and then when you click confirm the :form-state will be replaced with the :edit-state

emccue03:06:27

so thats is what updates :value

zackteo04:06:56

Right I was wondering specifically where the copying of state was done. And now I see it in enter-edit-mode :) Thanks @emccue for taking the time to answer my question so comprehensively! :D

Oliver George23:06:10

I think our re-frame event handlers fall into two catgories. 1. User event handler responding to user initiated dispatch (e.g. ::form-save-click) 2. Utility event handlers which provide some stand alone behaviour and often come in pairs (e.g. ::form-save, ::form-save-response) I'm thinking about whether clearly separate those two cases is a good idiom. Q: Which is better... • ui dispatched events mostly just dispatch to utilities (e.g. dispatches to ::form-save) • ui dispatched events do the the work possibly leveraging some pure logic utilities (e.g. calling (form-save-logic-helper)) • something else?

p-himik23:06:18

I'd argue that re-frame docs go against either. It's better to have UI-facing events that are intent-based, so ::form-save-click. And to separate the "util" events from the UI events, I simply add - in front of the former: ::-form-save-response.

Oliver George23:06:42

Nice naming convention. I like that.

Oliver George23:06:36

Would you have a stand alone ::-form-save utility handler or bake that behaviour into the ::form-save-click handler?

p-himik23:06:29

The latter. I'm not trying to abstract away things that don't have a need for that. I would do that only if ::form-save-click does something in addition to what ::-form-save is doing and is reused somewhere else.

Oliver George23:06:52

Thanks. I've certainly seen some tortured utilities where different use cases aren't quite the same.

emccue00:06:56

I would just never have utility event handlers

emccue00:06:22

the two cases for me would be user initiated dispatch and "external process" initiated dispatch

emccue00:06:00

so ::user-clicked-submit-form and then, ::form-successfully-submitted. ::form-failed-to-submit

emccue00:06:29

::user-clicked would be initiated by the user, the other two are initiated by the http goblin that lives outside of pure fp

emccue00:06:52

if you need a utility form-save, write a function instead

emccue00:06:10

(defn form-save [db]
  [[:http-xhrio ...info...]]

(rf/reg-event-fx
  ::user-clicked-submit-form
  (fn [{:keys [db]} _]
    {:db (... set loading flag ...)
     :fx (form-save db)}))

emccue00:06:33

(defn form-save [db]
  {:db (... set loading flag ...)
   :fx [[:http-xhrio ...info...]]}

(rf/reg-event-fx
  ::user-clicked-submit-form
  (fn [{:keys [db]} _]
    (compose-stuff 
      (constantly {:db (..set something..)})
      form-save)))

emccue00:06:49

dispatching should be tied to a real thing that happened

p-himik09:06:15

I think it's worth specifying that the above "should" is a matter of opinion and not a prescription from the docs. At least, I don't remember the docs saying anything of the sorts. And, obviously, I prefer having util events myself. :)