This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-02-03
Channels
How do you go about debugging an error list this? https://puu.sh/CGnmY/72db0997df.png
looking at the function in parser.cljc
it seems like it failed an expression to an ast, which I assumes is something fulcro calls on queries?
but I am only getting this error on a hard load (refreshing the page), and it doesn’t render properly. If I make an edit it triggers a hot reload and the error doesn’t appear (it runs the query fine and displays the data)
I found the source of the issue, but more by chance than by reading the error message. If anyone has debugging advice to share in this sort of scenario, please do 🙂
definitely get your devtools installed and working so you can read them better. Errors in the parser are tough, since it generally means either the query or the mutation you submitted either didn’t parse, or that the handler for the mutation threw an exception…you can be defensive and wrap your mutation action bodies in try/catch
Thanks! In my case the issue seemed to be coming from a malformed ident (one-element vector instead of two elements). This ident wasn’t sent as part of a query, but still triggered the error. Anyway, fixed!
Question: when setting the ident of a screen, why does :ident (fn [] (task-screen-path))
work but :ident task-screen-path
does not?
I see your confusion…the (fn ...)
part of defsc is NOT really a lambda…it’s syntax of defsc
@tony.kay AH. that explains it! I didnt; understand why I had to wrap it in a lambda that… did nothing
should have given you a syntax error on compile, since the non-lambda “look” is required to be a vector
yep it did; I was just wondering why, but if ito’s “not really a lambda” that explains it
you’re right…I could easily let the macro allow it, but was trying to make it super regular for better error messages when you do something wrong
the number of things that have to “align” is one of the more difficult annoyances of declaring components…the error checking of the non-lambdas is quite useful, and was my main goal
@tony.kay imho that’s fair, better error messages are preferable, but in this specific scenario the error message was definitely not understandable by a beginner like me. Perhaps if the macro gave something like the explanation you just gave me as an error in this case it would be better
The error message I got is:
> [ 0.785s] [fulcro.client.primitives] malformed Ident. An ident must be a vector of two elements (a keyword and an EDN value). Check the Ident implementation of component elogic-beyond.ui.root/Router-Union
.
oh and a warning: > [ 0.781s] [fulcro.client.primitives] get-ident returned an invalid ident for class: [object Object]
A Hacker News story made me think of Fulcro, specifically how Fulcro nicely addresses managing state across subsections of the UI tree: "React as a UI Runtime" https://news.ycombinator.com/item?id=19067302, and in particular this comment thread on state machines https://news.ycombinator.com/item?id=19068939
I've taken the liberty of creating a Fulcro stream on the Clojurians Zulip -- https://clojurians.zulipchat.com, https://clojureverse.org/t/introducing-clojurians-zulip/3173
@tony.kay Again on routing… I am having the following issue: on initial page load, the default route of my router briefly flashes before transitioning to the current route. This is (I assume) because there is a small time lapse during which the router is on its initial state and hasn’t yet been transitioned to the current route in response to the history event.
My first idea to tackle this was to initialise the router’s current-route under :initial-state
when building the Fulcro client (which I am trying out right now), but do you have another suggestion?
Basically I want to do a set-route*
before the initial render. I tried this but it lead to an error:
(fc/new-fulcro-client
:initial-state (r/set-route* {} :root-router (get-current-screen-path))
...
To break it down:
Current behaviour:
- On page load, application renders router’s default screen.
- After a brief moment, the history-listener machinery triggers a mutation which in turns triggers set-route*
, switching to the “current screen” (based on URL).
Desired behaviour:
- On page load, application renders router’s current screen (based on URL).
@tony.kay if you get the time to check out this question today / at some point, I haven’t found a solution to it
Main approach: Initial state starts with a “ready?” flag set to fasle…when not ready, show a “Loading…” screen. Application looks at URL, sets routes, THEN sets ready true.
Alternate: You do an external initialization of state before you mount the app, making sure the state is correct at start
@tony.kay I see, ok. So no router state manual initialisation; just don’t render the app until we have set the routes
I’ll try both of those approaches. I tried the second one (initialise the state) but got into a “bad route” error. I probably messed it up, will try again
@tony.kay I did it like this:
(fc/new-fulcro-client
:initial-state (r/set-route* {} :root-router (get-current-screen-path))
but that didn’t work (I was using r/set-route*
as a helper to initialise the state properly for the route)@tony.kay is the initial state provided there deep-merged with the initial state coming from components? or only shallow-merged?
@tony.kay I tried to initialise the state with a tree, by hand, like this:
:initial-state
{:fulcro.client.routing.routers/by-id
{:root-router
{:fulcro.client.routing/current-route (get-current-route)
:fulcro.client.routing/id :root-router}}}
(in new fulcro client) but it messed things up somehow:initial-state
{:fulcro.client.routing.routers/by-id
{:root-router
{:fulcro.client.routing/current-route [:SCREEN/home :singleton]
:fulcro.client.routing/id :root-router}}}
look at what a normal call to get-initial-state
would return from root: it is a nested tree data structure, NOT a normalized db
@tony.kay how does it merge the state returned by get-initial-state
on the root and the state given by :initial-state
on the Fulcro client?
so I guess I should call get-initial-state myself on the root, then maybe set my current route on that tree using an assoc-in, and set that as initial state
Here’s what I’d do: 1. Call get-intiitla state on root 2. Use tree->db to normalize it (using get-query on root for the query) and put that in an atom. 3. Use mutation helpers and swap on the resulting thing 4. Pass the final atom to the new client
That’s why I like the flag method better…just mark it as not ready, and queue a sequence of mutations
That allows me to also do it on the server-side though. I could even populate in route data
(not something I am going to do right now, but seems like it woudl work within that approach)
(I know you are not very familiar with HTML5 routing but I think it’s irrelevant here)
From what I can see @claudiu you are dealing with this in the router’s component initial state: https://github.com/claudiu-apetrei/fulcro-nav-router/blob/develop/src/main/fulcro_nav_router/core.cljc#L55 :thinking_face:
@claudiu I don’t fully understand your code though. Do you get the flickering effect I am having? And if so, what would be your approach to fix it?
The nav-router code with build-initial-state is because of dynamic-queries (for code splitting)
In the repo there is an example app. For intial-page-load I used the :started-callback
https://github.com/claudiu-apetrei/fulcro-nav-router/blob/develop/examples/simple-spa/src/main/simple_spa/client.cljs#L30
@tony.kay bit of an implementation-specific question: I need to trigger a transaction within a component then run a function AFTER the UI has re-rendered as a result of the transaction. Experimenting I found that this worked:
:onChange (fn [evt]
(m/set-string! this :ui/new-formula-value :event evt)
(js/setTimeout (fn [] (log/info "AFTER TIMEOUT"))
0))
this is a react-ism: https://stackoverflow.com/questions/26556436/react-after-render-code
so I would need to store the function I want to run on the component, and run it on componentDidUpdate?
depends on what you’re trying to do…pessimistic mutations cover most of the use-cases
but if you truly need to do something “after render”, then that’s limited by what React provides
Simple: I want to write an input component which allows you to enter special characters. It looks like this: https://puu.sh/CGL75/a4543e726b.png. When you click on one of those buttons it will add the character to the input’s current value, but I need to re-position the input carret (using setSelectionRange) after render, otherwise it won’t be where the user expects it to be
Hmm but then my input would be uncontrolled, which isn’t as nice to use from the parent, it seems
so, you can still make the component “appear controlled”, which is in fact what Fulcro does to all react inputs…but it’s a messy hack
right, I would need to add a componentWillReceiveProps lifecycle hook, and do messy things there, no?
so, in systems like Fulcro that want the control to come from another source of truth, you have to kind of hack it in
Why? Can’t you have an input’s value set from the result of a query, and spit a mutation in response to onChange
?
I mean, I am using the wrapper code I guess, but react inputs are controlled by default if you pass a non-null value to :value
?
@tony.kay there isn’t an example in their doc but unless I am terribly mistaken it works fine with global state; I have done it before. The component doesn’t care, really…
so component-local state is preferred since latency is very noticeable when you are typing in an input
If you try to control an input from an async update, then you;ll get cursor jump to end problems
Yeah, but I am getting that “cursor jump to end” problem either way with my input because of the extra buttons to add special characters. Anyway I don’t want to waste your time on this, you gave me enough pointers; I think I know how to fix it now 🙂
This is the code I had before, on my vanilla React version:
insertUnicodeSymbol = (sym: string) =>
() => {
const { value } = this.state;
const selectionStart = this.refs.input.selectionStart;
const selectionEnd = this.refs.input.selectionEnd;
const newValue = value.substring(0, selectionStart) +
sym +
value.substring(selectionEnd, value.length);
this.setState({
value: newValue,
}, () => {
this.refs.input.focus();
this.refs.input.setSelectionRange(
selectionStart + sym.length, selectionStart + sym.length
);
this.props.onChange(newValue);
});
}
Yeah, this is just a painful aspect of DOM sync with React…just have to understand the React aspects
Yep, but i was trying to keep the value in global state. I guess I’ll use give up on that and use component-local state
Unfortunately that is basically what you have to do because the React Magic is only supported via sync calls to setState; HOWEVER, there is nothing that prevents you from making that look controlled from outside the compontn
The trick is essentially to optimistically do the setState so that when the async value comes in from the “model” is already matches the value on the component
https://github.com/fulcrologic/fulcro/blob/develop/src/main/fulcro/client/dom.cljs#L119
BTW, you can leverage that function to add wrapping to you own (or js ecosystem) components…I do that for the react semantic UI wrappers
Ah neat! Can I use this even if my “Input” component doesn’t have an input at the top-level? Surely it must expose onChange
and other props?
@tony.kay ah yeah, quite a bit of fluff there. By the way I saw you using gobj/set
etc in the book. Any reason to use it over (set! ...)
?
setting by string is a safer thing to do in this case over adv optimization (prevents symbol rename)
much of the code in those areas was “inherited” from other places (e.g. Om Next), where I did not necessarily write it or have knowledge of the original authors intent myself.
By the way I just put together a light form library
that is very customizable and does basically everything for you 🙂.. you can check it out here
Why am I getting this warning and yet I can access this
? https://puu.sh/CGQKz/ddd1440c85.png
defsc
macro gives you access to this
but not props
(second argument)
Inside those hooks like :initLocalState
@U6Y72LQ4A ah; that’s odd. Is there any other way I can access the props?
yes, look at the primitives
namespace, there should be a get-props
or props
function given a component instance
@U6Y72LQ4A ah yep, I Ctrl-F’d it earlier for get-props
, but the function is named props
. Thanks!
@U6Y72LQ4A is there a technical reason for why the macro doesn’t make the props available?
don’t know 🙂
@tony.kay is there a technical reason for why the macro doesn’t make the props available? 🙂
React does not pass in props, so props are not there…same with all of the lifecycle methods (documented in docstring)
If there are cases where we “can”, I guess we could…it is an inconsistency in the macro from a “global reader” perspective that cannot be helped in some cases (e.g. get-query must be callable statically).
@tony.kay ah; because in the book you mention:
> There is no formal constructor for defsc, but you can get exactly the effect of a constructor by placing the code for it in :initLocalState, which is guaranteed to run once and only once per instance construction
and react does pass props to class constructors.
I believe back when React.createClass
was the norm people used getInitialState
, which indeed doesn’t have the props as an argument, but when using ES6 classes the state is usually initialised in the constructor, which does have the props.
to date there has been no real outcry for refining lifecycle methods…PRs welcome (assuming they don’t break existing apps)
I think you’ll find the a lot of the answers to “why” questions are not terribly satisfying 😉
@tony.kay haha. Well, I think if I want to contribute to documentation and/or code, understanding the “why” is often quite important, even if the answer is unsatisfying
Putting the destructured arguments into the lifecycle methods where possible is one place where we could make the macro better. Supporting symbols for lambdas is another.
but i was also concerned with other kinds of potential confusion: some lifecycles give “current” props as args, others give prev props…lots of confusion could ensure from doing anything automatic, which is part of the problem that I was “punting” on