Fork me on GitHub
#clojurescript
<
2021-05-17
>
Endre Bakken Stovner07:05:57

I am getting the following errors from https://github.com/endrebak/everclear/blob/working/src/cljs/everclear/dag/core.cljs, the most important of which I guess is

react-dom.development.js:68 Warning: React has detected a change in the order of Hooks called by everclear.dag.core.graph_page. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: 

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. useRef                     useRef
3. useEffect                  useEffect
4. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
Any hints on how to debug?

thheller07:05:48

@endrebak85 hooks have very specific rules. you must move the useState call into the inner fn or remove that inner fn entirely

thheller07:05:48

also your use of those hooks is incorrect. you definitely should not be calling set-graph as part of the render

Endre Bakken Stovner07:05:39

Hmm, I guess the set-graph can just be in a function of its own that listens to the sub since it does not need to add any hiccup, it just modifies an existing element. Thanks. Will try some more.

thheller07:05:14

you are trying to mix 2 entirely different rendering models. so you must read up on refs in react and them use them in a useEffect hook to wire up the graph stuff

thheller07:05:54

or go without hooks and just refs, that is fine too

Endre Bakken Stovner08:05:21

Thanks. I did not know I was mixing them ๐Ÿ™‚ Now I've gotten it to work with refs.

thheller07:05:24

the when-let is a problem as well. if the subscribe returns nil on the first try it'll never update if it becomes available later

Endre Bakken Stovner08:05:04

Thanks. I was just copying the luminus template which uses the following:

(defn page []
  (if-let [page @(rf/subscribe [:common/page])]
I thought it was a way to postpone the showing page until the data was ready.

thheller08:05:14

does that have the inner anon fn? I guess not?

Endre Bakken Stovner11:05:25

I am trying to translate the following code into ClojureScript (the javascript is a simplified version of https://github.com/ArquintL/d3-dag-example/blob/master/src/app.js):

const reader = d3_dag.dagStratify()
const dag_data = await d3.json(``);
reader(dag_data)
This is my attempt:
reader (d3-dag/dagStratify.)
data (->
      (d3/json "")
      (.then reader)) // failing line
I get the error:
Uncaught Error: can't stratify empty data
Am I doing something obviously wrong in my handling of https://clojurescript.org/guides/promise-interop or what?

niwinz11:05:29

On the js side you don't calling new on dagStratify(), so in cljs you dont need to use the . in (d3-dag/dagStratify.)

Endre Bakken Stovner11:05:18

Thanks, that seemed to do it ๐Ÿ™‚

Endre Bakken Stovner12:05:33

This is the first time I have to use go-blocks and the first time I've had to deal with promises, so I am a little confused. If I want to translate the following into a go-block, how would I do it?

const reader = d3_dag.dagStratify()
const dag_data = await d3.json(``);
reader(dag_data).then(d3_dag.sugiyama())
I've gotten this far:
(let [reader (d3-dag/dagStratify)
          sg (d3-dag/sugiyama)
          dunno? (go
                   (let [data (<p! (d3/json ""))
                         dag-data (reader data)
                         layout (<p! (sg dag-data))]
                     layout))]
I think everything but layout (<p! (sg dag-data)) is correct. But I am having a hard time translating this call:
reader(dag_data).then(d3_dag.sugiyama())

Chris Lowe12:05:28

from the gist:

loadDag(source)
  .then(useArquint ? arquint() : sugiyama())
  .catch(console.error.bind(console));

async function loadDag(source) {
  const [key, reader] = sources[source];
  const dag_data = await d3.json(``);
  return reader(dag_data);
}
It looks like loadData also returns a Promise (based on the immediate call to .then()). The last line of loadData is reader(dag_data) so your code equivalent of this line would need its own (<p!) call to yield the result:
(go
  (let [data (<p! (d3/json ""))
        dag-data (reader data)         ;; try wrapping in (<p! ) 
        layout (<p! (sg dag-data))]    ;; not sure if call to sg returns a promise? 
   layout))
โ€ฆor chaining then calls might be cleaner.

๐Ÿ‘ 3
thheller12:05:58

@endrebak85 don't waste your time on a go block for this, just chain the promises

(-> (d3/json "")
    (.then reader)
    (.then (d3-dag/sugiyama)))

๐Ÿ‘ 3
๐Ÿ™ 3
dnolen14:05:41

though you probably want error handling

๐Ÿ™ 3
dnolen14:05:01

and then depending on how far away the error handling needs to be, or how many handlers there may need to be

dnolen14:05:10

you may or may not want to do this w/ promises

dnolen14:05:36

but if it's not involved I agree chaining is fine

dnolen14:05:11

I much prefer go + try/catch + ex-info + causes

Endre Bakken Stovner15:05:01

I want to call the .links function on the object in a promise. I think I've tried every variation of the below, but nothing works

(.then graph.links) ;; (-> graph (.then (.links)) ;; ...
What is the correct way to do it? Below the promise-wrapped graph object is shown. It has a .links method, but I do not know how to get at it.

thheller15:05:59

(.then (fn [obj] (do-stuff-with (.-links obj))) or rather (.links obj), looks to be a function

๐Ÿ™ 3
Endre Bakken Stovner15:05:15

Thanks. In https://github.com/ArquintL/d3-dag-example/blob/master/src/sugiyama.js#L76 I'm trying to convert it is a function, but when I try to use it as a function, I get the error: Uncaught (in promise) TypeError: obj.links is not a function. Any ideas what might be wrong?

dpsutton15:05:08

console.log the item and see if it resembles what you expect

Endre Bakken Stovner15:05:45

I have done so:

{dag: LayoutDagRoot, width: 5.83843708165997, height: 8}
dag: LayoutDagRoot
dagRoots: (6) [LayoutDagNode, LayoutDagNode, LayoutDagNode, LayoutDagNode, LayoutDagNode, LayoutDagNode]
__proto__:
...
links: ฦ’ links()
So it should have a link method AFAICS.

dpsutton15:05:32

can you paste the exact code you are using to try to call it?

Endre Bakken Stovner15:05:17

Sure:

(.data (-> graph (.then (fn [obj] (js/console.log obj) (.links obj)))))

dpsutton15:05:19

that looks quite different that .data(dag.links()) above. and you're sure links: f links() is on the top level and not nested in the obj you are logging?

thheller15:05:36

graph also probably isn't a promise? what is graph? maybe you just want (.data (.links graph))?

dpsutton15:05:44

yeah from that code sample there's no promise stuff going on

thheller15:05:53

would help to have your full code, seems like you are making a couple mistakes

Endre Bakken Stovner15:05:59

Rendering a DAG is about the only thing I need to do in JS so I have not started reading properly about JS/ClojureScript yet. I'd love to include a Clojure-project in my PhD, but I am working against a tight deadline, so I am just trying to get stuff to work quickly.

dpsutton15:05:02

there's no use of then in the sample you are following. why did you introduce that

Endre Bakken Stovner15:05:43

Because I could not find a .link function and I assumed it was because it was wrapped in a promise.

Endre Bakken Stovner15:05:21

And if I try to log that graph, I am told it is a promise: [object Promise].

dpsutton15:05:26

so their dag is passed in. not sure how they got it, but perhaps they are calling (.then dag draw).

Endre Bakken Stovner15:05:31

The data is loaded with d3.json which returns a promise so I guess whatever they do after that also happens in a promise.

Endre Bakken Stovner15:05:51

But yeah, everything happens within a .then: https://github.com/ArquintL/d3-dag-example/blob/master/src/app.js#L18 That sugiyama function is what contains all the calls.

dpsutton15:05:48

so their function takes the value resolved from a promise, but you are passing that promise in. you'll need to correct that

Endre Bakken Stovner15:05:23

I thought once a promise always a promise. I looked for a way to unpack/deref a promise, I guess this is it: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve

thheller15:05:56

no, you (.then render-graph). the variable you named dag is actually the dag promise, so the only way to get the actual dag is to .then it and access it async

anonimitoraf15:05:54

There's also the <p! macro in cljs.core.async for 'awaiting' Promises AFAIK (tagged experimental though)

Endre Bakken Stovner16:05:50

Thanks. I should learn more about <p! too ๐Ÿ™‚

thheller15:05:22

(defn render-graph [data]
  ...)

(defn graph-page []
  (-> (d3/json "")
      (.then render-graph))
  [:div
   [:h1 "hiya"]])

thheller15:05:48

this is still widely incorrect regarding the react use but at least in render-graph you don't have to worry about promises at all anymore.

Endre Bakken Stovner16:05:10

Thanks all, now I am getting closer. I just need to find out how I'm mishandling react. Btw, this is for a project I'm hoping to publish in bioinformatics (or at least biorxiv) by the end of the year. I will discuss Clojure in it too. What channel would be most appropriate for asking about feedback on my discussion/summary of Clojure?

dpsutton16:05:55

if it's a general "about clojure" type thing, probably off-topic. if you write an article there's an #news-and-articles channel as well

๐Ÿ™ 3
Endre Bakken Stovner16:05:03

I cannot find any documentation about the use of curlybraces below (taken from here: https://github.com/ArquintL/d3-dag-example/blob/master/src/sugiyama.js#L102):

({x, y}) => `translate(${x}, ${y})`)
;^^^^^
What is the correct way to translate this? In a node repl, that function does not seem make sense, but I might be invoking it incorrectly:
> var f = ({x, y}) => x + y
undefined
> f(1, 2)
NaN

localshred16:05:42

In JS that's destructuring a single object argument to get keys x and y from the object and bind them as local vars to the same names as the keys

localshred16:05:51

So in JS you'd call f like this:

localshred16:05:12

f({x: 123, y: 456})

localshred16:05:14

cljs has clojure map destructuring in the argslist, but unfortunately (as @U05224H0W points out) you can't destructure a JS object in cljs in the argslist, you'll have to bind it to a local arg name and pull out those values yourself

localshred17:05:47

an equivalent non-destructured JS implementation of f is

localshred17:05:09

const f = (obj) => obj.x + obj.y

Endre Bakken Stovner09:05:27

Thanks. Weird that googling "anonymous function js curly braces" returned nothing relevant

thheller09:05:34

really not specific to anonymous function in any way

๐Ÿ‘ 4
thheller09:05:43

let {x, y} = obj; is also valid

thheller16:05:33

this is destructuring for a JS obj, can't do that in CLJS. but (fn [obj] (let [x (.-x obj) y (.-y obj)] ...)) works

๐Ÿ™ 3
Nathan Tuggy19:05:42

Iโ€™m starting a REPL with clj -M --main cljs.main --repl and using Firefox (88.0.1 on Mac) as my default browser, but the REPL immediately freezes as soon as I try to evaluate anything at all, and I can only quit it by Ctrl-C (not Ctrl-D). Deps.edn only has ClojureScript 1.10.844, nothing else. This hang doesnโ€™t happen with Chrome, but whatโ€™s wrong with ClojureScript in Firefox?

zane20:06:34

Did you ever get this resolved?

Nathan Tuggy22:06:59

Not sure, and I definitely never figured out what the problem was. I can use shadow-cljs OK now.

dnolen19:05:46

@ntuggy just tried with 88 no issues

dnolen19:05:14

and 88.0.1 as well

Nathan Tuggy19:05:42

Thanks for checking. Any ideas on figuring out where the problem is? Nothing shows up in the browser console, even in Safe Mode. (Also, itโ€™s not just Firefox; I started with Pale Moon, a Firefox fork, and only switched thinking it might be some obscure compatibility bug.)

dnolen19:05:58

hrm I really don't know - something environmental for sure

dnolen19:05:24

if you just need/want to dev ClojureScript this doesn't seem essential

dnolen19:05:36

i.e. dev in Chrome - test all browsers (no REPL)