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
I saw that the todo example does have edit - but that seems to work because the local state is stored in that component
Don't
or rather
lets say your app-db looks like this
{:some.ns/page-state {:first-name "" :last-name ""}}
you can write a subscription to get the current page state
(let [page-state @(rf/subscribe [:some.ns/page-state])]
...)
and then pass that to a component
(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])
and then have events that fire when a user performs an action
(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)}))
and dispatch those events when the user does a thing
(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])
it feels silly for events that just do an assoc, sure
but its a good general model
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
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
This worked. But now im unsure what to do if I want to edit my form data
i would treat that as an approach to take only when it has proven to be a performance concern
otherwise just avoid local atoms alltogether
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
thats a practical compromise
but what if i only want to edit the data in the re-frame db when i confirm it ?
why would you want that?
or actually
nvm i see the use
what i've done in the past is have
{:form-state .... :editing-form-state nil}
and when they start editing i copy over the form state and work in the editing form state
then when they slap submit, put the editing state in form state
how do i do that?
if I am interpreting your semantics correctly, you have a page where the user takes some action that puts them into "edit mode"?
and then if they click cancel none of what they did applies
and if they click submit they have edited the form
yeah something like that
okay so
{:some.ns/page-state
{:form-state {:first-name "" :last-name ""}}
{:edit-state nil}}
start with this
(defn editing? [page-state]
(some? (:edit-state page-state)))
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
(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])
(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)))))
there we go
the data is in your app db, you can copy it with an assoc
you see that in :some.ns/enter-edit-mode
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
sorry tabbed out
the on change calls edit-first-name
the view depends on the value of :first-name in the :edit-state
so when the event fires the view will be re-rendered with the new value
and then when you click confirm the :form-state will be replaced with the :edit-state
so thats is what updates :value
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
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?
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. :)
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
.
Nice naming convention. I like that.
Would you have a stand alone ::-form-save
utility handler or bake that behaviour into the ::form-save-click
handler?
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.
Thanks. I've certainly seen some tortured utilities where different use cases aren't quite the same.