Fork me on GitHub
#fulcro
<
2017-11-07
>
roklenarcic09:11:43

form's commit-to-entity! might have a weakness. It checks if form is valid outside of transact!, to construct the transaction. In a multithreaded environment that might become a problem?

roklenarcic09:11:34

also I still haven't figured out how to chain additional mutations to commit-to-entity! if it succeeds. I'd say that's a very common use-case.

eugekev12:11:13

Hello all, hope all are well. I noticed a few interesting things and would welcome people's thoughts. (1) I moved over code from a not-quite-properly built fulcro codebase to one built with the fulcro lein template (thanks Tony!) and noticed that 'classes' inside cljs files were not being seen in the build process. Changing them to .cljc worked well. This is interesting (if it's a design decision) because the UI code is very much cljs only, correct? Is cljc more appropriate for a certain reason in this case? (2) Too, the upsert-css function was not being found despite importing it correctly and I wonder if this is because I was (now) calling it from a cljc? (Hope I'm not being dumb here).

claudiu12:11:17

@eugekev have you tried lein clean and rebuilding everything ? .cljs should work just as well, cljc only if you have ssr.

mitchelkuijpers13:11:16

@eugekev upsert-css is cljs only so you should put that in a reader conditional

eugekev13:11:49

Thanks @claudiu I'm pretty sure I did. @mitchelkuijpers - will definitely do so, thanks. I took it out for the moment to see if it worked and that was fine. Perhaps the lack of the reader conditional in the cljc was the problem. 🙂 Cheers to you both.

eugekev14:11:47

@claudiu Yes, even after a lein clean, renaming the filename for the component from .cljc to .cljs causes a

Exception in thread "main" java.io.FileNotFoundException: Could not locate test_fulcro/ui/homepage__init.class or test_fulcro/ui/homepage.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name., compiling:(root.cljc:1:1)

claudiu14:11:55

@eugekev did you rename all the files UI to cljs ? looks like there's some component on the "server" side that tries to import that one. The template has ssr, on the server side in server.clj need to be remov those imports and "def top-html" to generate only the container div.

tony.kay16:11:49

@roklenarcic commit to entity is not really what you should use as your primary tool for most forms, because it isn’t composable with other mutations. The stuff in the dev guide about custom form submission is probably going to be a more common thing. Now, on “how do I tell if the submit went ok?” You have several options. The first thing you should do, though, is change your thinking a bit. Admit you’re in a distributed system. A submit is a write to a remote. A result is a read from a remote. Fulcro’s model is one where data cannot flow in unless there is an explicit query (how else would it know where to put that data?) So, mutations can have a return value. If you want to use that route, then you have to make custom form submission mutations, and configure the mutation merge functionality so you can capture the returns and do something with them. You could also simply treat your “status check” as an explicit read. Fulcro sequences mutations first on the network, and this would give you the explicit query for proper merge (and post mutation support).

tony.kay16:11:59

BTW: To anyone lurking: this is one of the core concepts about any kind of “result” (read after write). In Fulcro, if you have to know the result of a write, you have to explicitly read it, or add a merge handler for it…otherwise there is nothing Fulcro can do with the result..it’s your database.

tony.kay17:11:36

In REST-based systems, we were used to “response codes” and such from our POST. If you think about it, that is a much more complicated way of dealing with results. There is no structure to it. Every response from POST had a different JSON blob of explanation with no schema. It implies we’re going to lean on the server for validation (way old-school). The interface to it leads to callback hell because you’ve got async stuff going on. There are these weird codes that you may or may not have mapped properly from the standard. There is the fact that a lot of them would just be ignored anyway (we trust the server is going to succeed most of the time, and often program that way). With Fulcro, unifying all input from the server around queries and responses is what gives us the ability to reason about the data model in a very simple and consistent way (it is just a normalized graph). Finally, and this requires a bit more work, but might be worth it: Why is your form submit failing at the server??? I’ve talked about error handling before. Your code really should be robust, so except for bugs your submit should work. You’ve got the minimum delta. Most apps can tolerate last-write-wins. There is nothing that can fail that you can do anything about….except network errors. Recoverable network errors imply retry. So, I’m hoping that we start to evolve our ways of interacting with users to where they only see bad results from the server where there is something seriously wrong…but at that point they’re just going to have a bad experience, so some kind of global error handler that pops up a modal of “things went south in a bad way” become sufficient. This is a good result 🙂

tony.kay17:11:49

(less error handling code to write/test)

tony.kay17:11:58

So, the model I’d suggest is this (and I’m working on making this less code for you to write in 2.0): 1. If you need to ask about the result of something use explicit remote reads that you queue with your write. You’ve now given an explicit schema to your interaction. 2. Make your server submissions idempotent by saving the uuid of tempids in your database (that way if you managed to write it on the server before the error occurred, you can just re-send the tempid remaps). Similar scheme for other submissions… 3. (2) allows you to write a custom network handler that can safely auto-retry on network errors. 4. Add a global error handler to (3) that knows about the reconciler, and can trigger a modal when things go very badly (or you want to give up on retries) 5. (coming soon). With history it is even possible to rewind the user’s session in the UI to the last place you were able to talk to the server. This is not entirely possible yet, but will be with 2.0. Give them the option to re-submit the entire failed sequence (it will be in history), or just resume at the last known good spot.

tony.kay17:11:45

I think an acceptable (and easier-to-write) version of this is just to assume things will go well, and have a global network error handler than can report when things have gone south and re-start the user in a decent known state (maybe even returning them to a home page for code simplicity). It is rarely going to happen unless you have lots of bugs, so most ppl will find this quite an acceptable experience. In this model the server just throws an exception when it is unhappy, and triggers that global error handler.

tony.kay17:11:25

(5) is going to blow people’s minds 😄

roklenarcic17:11:34

Why is your form submit failing at the server??? -> if server is down I don't want to close the form. What I was hoping for is simply an ability to say: if fallback handler wasn't triggered then run this mutation vector.

roklenarcic17:11:58

I'll admit it's a much smaller scope of considering error handling than what you wrote

roklenarcic17:11:46

I wasn't looking to handle any special error circumstances

roklenarcic17:11:10

I think when something goes south and you've clicked save/submit in a dialog, you'll want to not run the "dialog close" mutation. The error popup is handled by fallback function obviously

tony.kay17:11:02

You’re assuming synchronous operation (wait to close the form until I know it got there)?

tony.kay17:11:15

(because you’re in an optimistic system by default here)

tony.kay17:11:56

You have to force synchrony if you want it…

roklenarcic17:11:04

I see what you're getting at.

roklenarcic17:11:50

Honestly error handling is one of the pain points of software development right now

tony.kay17:11:21

So, here is the way to do that: 1. Submit the form, and disable all user interactions (e.g. with a state variable like :ui/submission-in-progress?) 2. Send a load to ask about the result… 3. The post-mutation on (2) flips the :ui/submission-in-progress? back to false, and can optionally auto-close the form.

tony.kay17:11:41

You can do exactly what you want in Fulcro, just remember what the default model is 🙂

tony.kay17:11:57

If you want to block progress…well, block progress

tony.kay17:11:07

simple as that 😉

tony.kay17:11:52

I agree with you about error handling…I think we actually have a really great story, but it is different

roklenarcic17:11:54

Bugs are not a problem for me. I don't expect error states based on bugs

roklenarcic17:11:43

But you did alert me to the possiblity of user clicking save button multiple times while requests are in flight

tony.kay17:11:57

yep…that’s something you have to deal with 🙂

tony.kay17:11:38

I generally “block UI” with soemthing like a transparent overlay that doesn’t bubble events

tony.kay17:11:46

that way I don’t have to code it all over the place

roklenarcic17:11:29

Error handling stories are a funny thing. OOP brought exceptions but in experience of last 10 years, nobody ever attempts to recover from them

roklenarcic17:11:47

they just wrap try catch -> log exception

roklenarcic17:11:01

I think it's why Rust lang went back to C style error codes

roklenarcic17:11:49

I find error codes to be superior to exceptions because people tend to aggregate several exception scenarios into one exception class, and it varies by exception text, which is terrible data to dispatch on.

roklenarcic17:11:16

Having exception messages on exceptions themselves is quite a blunder IMO

roklenarcic17:11:32

The number of programs I've seen that blurt out exception message (in english, even when locale is not so) to the end user in my career is staggering

roklenarcic17:11:17

But I'll look into your suggestions. I'm not making a very complicated app and it doesn't have complex exceptional scenarios.

roklenarcic17:11:59

The "song" column contains a Hall and Oates song. The idea that Hall and Oates are software gurus is controversial in some circles, so you can treat this as flavortext.

tony.kay17:11:59

@sundarj I had not. Doesn’t look like much has been done on it

roklenarcic17:11:17

As a Hall and Oates fan I appreciate that

tony.kay17:11:04

and it is 20 lines of code…

roklenarcic17:11:34

Although they should have found a way to use Maneater 🙂

sundarj17:11:26

yeah not at all complete by the looks, but i think it's interesting

roklenarcic17:11:43

not at all complete by the looks -> quite an understatement

roklenarcic22:11:14

Uh... I might be wrong (I don't know how to debug clojurescript), but forms validate-fields function, when used on a form with no fields other than a subform that has actual fields, returns a value that forms valid? returns false. It returns a value where the form, that contains no fields other than a subform, has all non-id fields marked as :unchecked.

roklenarcic22:11:45

I've been wrangling with this whole day and I don't know what to do.

roklenarcic22:11:28

I see what's going on. commit-to-entity! checks if form is valid with validate-fields which doesn't handle sub-forms, so they are returned as unchecked, so the first time commit-to-entity! is called, only validate-form is called. The second time I click the button, form is marked as validated and the entity is sent to server

tony.kay23:11:20

@roklenarcic There’s likely an improvement that can be made there. The idea is that your commit button should not be available until you’ve actually run validations on the form and the form is in state as valid. If commit detects that this is not the case, it runs validation and bails on the commit.

tony.kay23:11:32

which it says very clearly in the doc string of the function

tony.kay23:11:14

you are using an editor/ide that shows you docstrings, right?