Fork me on GitHub
#reagent
<
2021-10-20
>
hanyuone06:10:13

I'm trying to translate https://robkendal.co.uk/blog/2020-02-20-creating-a-react-code-editor-and-syntax-highlighter over to a CLJS project, but I'm having trouble with writing a nice Reagent version of:

useEffect(() => {
  Prism.highlightAll();
}, [props.language, content]);

hanyuone06:10:41

I have a (def content (r/atom "")), but there doesn't seem to be a way of just calling Prism.highlightAll() whenever content is updated - r/track doesn't seem like it'll do exactly what I want, since I'd need to actually include @content somewhere in the function body for r/track, but Prism.highlightAll() doesn't depend on @content at all

hanyuone06:10:16

Also, how would I translate this:

import Prism from "prismjs";

Prism.highlightAll();

David Pham06:10:52

To answer your last question, something around the lines (ns app (:require ["prismjs$default" :as prism])) (.highlightAll prism)

hanyuone07:10:08

Got an Uncaught TypeError: Cannot read properties of undefined (reading 'highlightAll') when I did (.highlightAll prism)

hanyuone07:10:45

Also couldn't I use something like (:require ["prismjs" :default prism]) in shadow-cljs?

p-himik07:10:33

Yes, that's exactly what you'd use in shadow-cljs. There's a whole section on using NPM packages in the documentation: https://shadow-cljs.github.io/docs/UsersGuide.html#_using_npm_packages

hanyuone07:10:17

Yeah, it throws the same error of Uncaught TypeError: Cannot read properties of undefined (reading 'highlightAll') when I use :default instead 😞

p-himik07:10:58

Just read that section, it has an answer. :default is not always the solution - depends on the package.

p-himik07:10:56

There's a table with JS->CLJS imports translation. But it can be used in only about ~80% of the cases (just eyeballed the number based on my own experience). The rest is described by the text within that section.

thheller07:10:44

FWIW consider :default deprecated. It was a non-standard addition and $default is the now standard way which shadow-cljs also support, so thats what you should be using if anything. I'll update the docs accordingly soon hopefully.

🙂 2
👍 6
hanyuone07:10:26

@U2FRKM4TW I used ["prismjs" :as prism] since the "prismjs" package seems to be able to be require()d normally as well, but (.highlightAll prism) now seems to not do anything at all

p-himik07:10:15

Use (js/console.log prism) and see what it gives you.

thheller07:10:15

I doubt this has anything to do with how you require it

thheller07:10:32

and very much how you use it. you are using a react based rendering but prismjs doesn't work that way

hanyuone07:10:38

@U05224H0W is there a way of copying this functionality from the tutorial I was following into cljs?

useEffect(() => {
  Prism.highlightAll();
}, [props.language, content]);

thheller07:10:54

yes, exactly like that. I mean translate it from JS 1:1 and then lookup how to use hooks in reagent

hanyuone07:10:14

So I'd have to use react/useState and react/useEffect in my Reagent component too instead of messing around with the r/atom?

thheller07:10:41

no clue, I'm not current on how reagent works with hooks

tomrbowden07:10:06

@U013HB9EFT8 You don’t need to use React hooks to get this to work. (1) from your console log of prism, it shows that highlightAll is a field on the prism object (which you got from the require alias ["prismjs" :as prism], with a function value that takes no arguments. So, you call it simply like this in CLJS: (prism/highlightAll). (2) Since Shadow-CLJS does not load CSS dynamically into your src (at least, not by default), you will need to copy the Prism CSS file from node_modules into your resources/public CSS file. (3) I have found for the highlightAll , you will need to push it onto the event callback queue so that it runs after the DOM is updated, which you can do using a setTimeout (also described in https://betterstack.dev/blog/code-highlighting-in-react-using-prismjs/)

tomrbowden07:10:04

I’ve made a minimal example of Prism using Shadow-CLJS and Reagent:

thheller07:10:55

do not use setTimeout!

tomrbowden07:10:27

@U05224H0W Ah, what should I use instead?

thheller07:10:02

the correct solution is either hooks or the usual old component lifecycle hooks such as :component-did-mount or :component-did-update

tomrbowden07:10:10

I’ve tried using ;component-did-mount without the setTimeout, and it didn’t work…

thheller07:10:23

never ever use setTimeout to trigger a side-effect of rendering since there is absolutely no guarantee it'll run when you intended it to

thheller07:10:59

you can also just use a ref. I'd assume that prism has a highlight(domNode) in addition to highlightAll

tomrbowden07:10:28

OK, I will try to rewrite using refs…

tomrbowden07:10:44

Yes, I believe it has @U05224H0W

tomrbowden08:10:13

Thanks for the warning about setTimeout :thumbsup:

p-himik08:10:44

requestAnimationFrame is probably better than setTimeout, right?

thheller08:10:54

no, even worse

p-himik08:10:54

Not for this particular case, but in some others.

p-himik08:10:58

Oh, how so?

thheller08:10:58

react gives you ways to "hook" into the lifecycle at the point when you need it to. use them. do not ignore the lifecycle and just fire off async events in the hopes that they trigger after the lifecycle is actually done

thheller08:10:47

especially with react concurrent mode and things like suspense there is no guarantee that a setTimeout (or any other) doesn't run before something is actually rendered

thheller08:10:45

there is always a "you are guaranteed this node is now mounted in the document" point and callback. that is the time you want to do stuff.

thheller08:10:53

everything else is just a hack, which might work but has no guarantee to do so and may break randomly for users with slower devices or slower internet connections that you use when testing

p-himik08:10:03

> before something is actually rendered Sounds like using such functions should still be fine when the delayed functionality does not rely on having anything rendered, right? E.g. changing the state of a component, like in this contrived example:

(defn view []
  (let [x (reagent/atom 0)]
    (fn []
      (js/setTimeout #(reset! x 1) 0)
      [:span @x])))
Just to be clear - I'm not trying to justify anything, just trying to understand the boundaries.

thheller08:10:43

that'll trigger itself in an endless loop

p-himik08:10:54

No it's not. :)

p-himik08:10:03

The second reset won't render anything because 1->1.

thheller08:10:03

yes it does

thheller08:10:28

but it'll still trigger reagent update logic, which needs to compare

thheller08:10:46

didn't say it'll render every time. I said trigger endlessly

thheller08:10:50

ah hmm no I guess you are right. the watch doesn't call render again probably

p-himik08:10:51

That is still wrong. Reagent won't run the inner function the third time. It runs only when the properties are changed.

thheller08:10:08

yeah forgot the inner fn

thheller08:10:41

still a really bad way to do this given that you render one thing and then immediately another

thheller08:10:27

well don't have time to get into a debate about react/reagent rendering models 😛

p-himik08:10:09

No arguments about it. Although I do have such a line in my own code. Don't remember the details, but none of other methods worked at all for some reason. Probably not so much because of React but rather some Reagent peculiarities since it all had to do with ratoms.

p-himik08:10:34

And of course removing that line right after this discussion simply magically worked. :D