Fork me on GitHub

@slawek098 just assoc-in it into state…it’s all maps and vectors. The normalized-state namespace has a few more helpers…but in general it’s pretty simple stuff.


for to-many on merges you can use the named parameters of a number of calls to merge methods, like :append. There are also data targeting merge types. See the book


and of course I broke cljdoc again 😞


about to do some drag’n’drop things and I’m curious if there are any fulcro examples or suggestions? I’m betting this is a great use case for a state machine!


@thosmos so, I’ve done a bit of that…HTML5 does so much for you that it is really more about tracking real DOM elements and using refs from React

👍 4

I ended up making a global atom to hold node id -> DOM refs so I could register the relevant DOM elements into an easy place to find them, so that I could do things like measure where I was relative to them on screen


Fulcro ends up not really doing much itself…just a mutation at the end of the drop


ok that makes sense, to keep it out of the app state DB until an actual drop happens


I remember there being a bunch of crap you had to do for DOM purposes, but completely unrelated to Fulcro…read docs on HTML5 DnD very carefully is my advice…it was tricky to set up, but relatively easy to use


some event prevent defaults and that kind of thing


any experience with react-dnd from npm? It says it is a HOC pattern that injects props. I’m guessing I’d leverage the HOC approach from the guide if I were to attempt that? Hmm, it looks a bit heavy, uses Redux internally … meh, I think I’ll start with doing it directly and see how it goes …


@thosmos you’d have to verify yourself but I think workspaces uses some react library to do it’s drag and drop stuff


oh thanks for the pointer


@U066U8JQJ thanks for the info. I spent a good amount of time admiring that very code last night. What makes it even easier to use is workspaces is already a dep via fulcro.


Hi, I need to refresh a page every second or so and I do that at the moment with js/setTimeout when the component is rendered and this calls a mutation. This all works fine as long as the data is changing each time, that causes a re-render and thus a new timer being set. But when the data doesn't change on a subsequent call the component doesn't get re-rendered and the new timer isn't set.. and no more updates. What is the best way to address this?


How can I see to which version is fulcro book referring to? Because some functions mentioned here do not exist in most recent version, for that matter.


Like merge/integrate-ident does not exist in most recent version.


And in every version I try, it seems like those functions are moving around.


@thomas If you really need an external source of events, then a setInterval at the top-level that does the data merge (via app) is better. Add in a UISM and you can use abstract events to control it and actors for UI elements to generalize it 😉…oh, and since UISM has timeouts you could skip the setInterval as well.


@slawek098 Sorry about that. I need to update the book, and it was not my intention to make any API disappear. I usually deprecate old names and point them to the new location.


When I try to integreate ident into list with

swap! state
       (data-targeting/integrate-ident* [:wire/id wire-id] :append [:wire-list/id wire-list-id :wire-list/wires]))
I am getting error like:
core.cljs:159 ERROR [com.fulcrologic.fulcro.algorithms.tx-processing:348] - Failure dispatching optimistic action for AST node {:com.fulcrologic.fulcro.algorithms.tx-processing/idx 0, :com.fulcrologic.fulcro.algorithms.tx-processing/original-ast-node {:dispatch-key wires.mutations/add-wire, :key wires.mutations/add-wire, :params {:wire-list/id :my-wires, :wire/id 2}, :type :call}, :com.fulcrologic.fulcro.algorithms.tx-processing/started? #{}, :com.fulcrologic.fulcro.algorithms.tx-processing/complete? #{}, :com.fulcrologic.fulcro.algorithms.tx-processing/results {}, :com.fulcrologic.fulcro.algorithms.tx-processing/dispatch {:remote #object[wires$mutations$remote], :action #object[wires$mutations$action], :result-action #object[Function]}} of transaction node {:com.fulcrologic.fulcro.algorithms.tx-processing/id #uuid "15e89e41-e739-48ae-939d-9e7393f18c79", :com.fulcrologic.fulcro.algorithms.tx-processing/created #inst "2020-01-16T16:36:00.198-00:00", :com.fulcrologic.fulcro.algorithms.tx-processing/options {:optimistic? true, :component #object[Component [object Object]]}, :com.fulcrologic.fulcro.algorithms.tx-processing/tx [(wires.mutations/add-wire {:wire-list/id :my-wires, :wire/id 2})], :com.fulcrologic.fulcro.algorithms.tx-processing/elements [{:com.fulcrologic.fulcro.algorithms.tx-processing/idx 0, :com.fulcrologic.fulcro.algorithms.tx-processing/original-ast-node {:dispatch-key wires.mutations/add-wire, :key wires.mutations/add-wire, :params {:wire-list/id :my-wires, :wire/id 2}, :type :call}, :com.fulcrologic.fulcro.algorithms.tx-processing/started? #{}, :com.fulcrologic.fulcro.algorithms.tx-processing/complete? #{}, :com.fulcrologic.fulcro.algorithms.tx-processing/results {}, :com.fulcrologic.fulcro.algorithms.tx-processing/dispatch {:remote #object[wires$mutations$remote], :action #object[wires$mutations$action], :result-action #object[Function]}}]}
Error: No item {:fulcro.inspect.core/app-uuid #uuid "cafe0290-8a34-48ed-b16d-fd91ee130f6d", :com.fulcrologic.fulcro.application/active-remotes #{}, :wires [:wire-list/id :my-wires], :terminal/id {0 {:terminal/id 0, :terminal/connector "JST.0", :terminal/pin 1}, 2 {:terminal/id 2, :terminal/connector "SCREWED.0", :terminal/pin 0}, 1 {:terminal/id 1, :terminal/connector "JST.0", :terminal/pin 2}, 3 {:terminal/id 3, :terminal/connector "SCREWED.0", :terminal/pin 1}}, :wire/id {0 {:wire/id 0, :wire/label "Vcc", :wire/color "red", :wire/terminals [[:terminal/id 0] [:terminal/id 2]]}, 1 {:wire/id 1, :wire/label "GND", :wire/color "brown", :wire/terminals [[:terminal/id 1] [:terminal/id 3]]}}, :wire-list/id {:my-wires {:wire-list/id :my-wires, :wire-list/wires [[:wire/id 0] [:wire/id 1]]}}} in vector of length 2
How should I understand that?


@tony.kay well, I think you're doing enough awesome work, there's no need to sorry 🙂


Thanks, but I complain quite a bit about how crappy the error messages are in Clojure, so I’m always trying to make it better, not worse. In this case, it could use improvement, which I will do right now.


on your error:


you’re calling swap! incorrectly


I love the feel that Fulcro is like "top-down" framework for developing apps quickly.

👍 4

swap takes a lambda…you’re giving it the return value of a function


drop the extra parens


It's always PEBKAC...


The TX processing system runs mutations, and it crashed….it is telling you which node it was processing, which is a lot of info…should be a more concise error msg


“Failure dispatching optimistic action” Means the action part of a mutation failed


original-ast-node {:dispatch-key wires.mutations/add-wire
is the mutation being worked on


the rest should probably be elided


3.1.5-SNAPSHOT has an improved error message for that now 🙂


and glad you’re liking Fulcro 🙂


Thank you very much for all the explainations!


Is this generally good practice to manipulate state manually? Or should I use those helpers?


And does it differ on server and on frontend?


Wait, on server I need this "state management" only because I am not using any database.. OK, nevermind.


Is this regular approach to modify state on both frontend and backend? Like, I have both frontend and backend mutation resulting in essentially the same state on both frontend and backend


@slawek098 I would recommend you use good coding habits in mutations..the ones you use in every other programming language and program: things like readability, ease of maintenance, lack of complex code duplication, etc. I’d much rather see something like:

(defmutation complex-operation [params]
  (action [{:keys [state]}]
    (fns/swap!-> state
      (snarf-the-underworld params))))
than a bunch of individual integrate idents, etc. Often, I’ll make a helper (for testing) called mutation-name* (`complex-operation*` for the example above) (so there isn’t a symbol conflict), and write:
(defn complex-operation* [state-map params]
  (-> state-map
    (snarf-the-underworld params)))

(defmutation complex-operation [params]
  (action [{:keys [state]}]
    (swap! state complex-operation* params)))
and then I have a super easy ref-transparent function of state-map to use in tests, and the mutation itself is always a one-liner.


That also has the advantage of making the elements of the mutations (and even their sub-operations) reusable everywhere.


not sure I say that well enough in the book: The * suffix notation is just a way to get around the fact that I already am using the base symbol as a mutation name, but want to make a separate implementation function with the same name.


What about those mutations on both frontend and backend?


Because I achieve the same effect by writing both backend and frontend mutations OR writing only backend mutation and reloading the page.


I don’t understand the question: are you saying you’re using a client-like in-memory database on the back-end? If so, you could put those helpers in CLJC.


Imagine I am adding new element to list. I've had frontend mutation adding element to list and executing backend mutation on remote.


Then, on backend, I've got another mutation adding element to database.


But I already have resolvers for all the data from database.


So it feels like I am adding element to list independently on both backend and frontend.


ah, your choice: If you want fast UI experience, add them on front-end. if you don’t care about the speed, then you can do the latter: let the remote handle it and perhaps even return the type for auto-merge on change


Are there both those cases described somewhere in the book?


not sure if there are specific examples that spell it out exactly that way, but all the primitives are well-documented


optimistic actions are always optional for full stack. You can always choose to reload your list after a remote operation (which you would code, for example, in the ok-action instead of action )


the optimistic stuff is about making it look instantaneous to the end user when your servers are a bit laggy


And how do you handle failure in backend?


again, up to you 🙂


if you’re doing optimistic actions on front-end, then techically you should have an error-action that reloads the list and probably shows a UI error…and you need to make sure your “definition” of errors is understood by the platform.


or you could have an ok-action that looks at the network result, and do “softer” errors…


or you can be pessimistic and not do anything until it is done…at which point you still want error and ok actions if you want to be completely pedantic about it.


Another choice is probably more common: Make a global error handler that shows the error message and does something more drastic…and make sure the only way you get those errors is on really serious stuff (which is most common)….then your assumption is “errors are rare, this is a good enough user experience”.


Thank you!

👍 4

Thank you @tony.kay I'll look into it.


@tony.kay Looking at the new keyframe-render2 stuff - how could we go about optimizing rendering when using state machines? Since you don’t call transact directly when modifying actors/aliases, there is no place to specify :update-only. Are you planning to add this? If not, I have a couple of ideas how it might work, and I could take a stab at it