reagent

juhoteperi 2024-11-11T09:04:33.208329Z

I answered about the plan for Reagent React 18 compatibility (or lack of it) in this Github discussion: https://github.com/reagent-project/reagent/discussions/611 I have probably written the same points somewhere previously. I guess it would be nice to mention something about this in the readme also.

👍 4
Harold 2024-11-11T16:04:41.086649Z

Thanks for that, and all your efforts. It's very helpful to get such updates. 🙂

Macroz 2024-11-11T18:34:09.709849Z

Definitely thanks for all the maintenance throughout the years. 👍 I kind of feel like that this is the end for the decade of no front-end churn for me. Reagent has been as stable as Clojure. Looks like pretty much all my serious projects need to consider a transition to another framework now?

DrLjótsson 2024-11-11T18:39:32.935279Z

If I am happy with the current state of affairs using re-frame and reagent, do I need to think about this?

Macroz 2024-11-11T19:43:23.659889Z

Well I'm thinking long-term. Some day React won't work with Reagent anymore unless someone steps up with maintenance, or an easy migration path or wrapper appears on top of the others.

juhoteperi 2024-11-11T19:51:23.171619Z

UIx has really nice path, you can start using it alongside of Reagent for new code very seamlessly

Macroz 2024-11-11T19:53:19.796799Z

Is there some guide for Hiccup in Uix?

juhoteperi 2024-11-11T19:54:52.672899Z

You wouldn't use Hiccup in UIx. You would just use UIx $ elements inside Reagent, and maybe Reagent elements inside UIx elements:

[:div {...}
 ($ :div {...}
    (r/as-element [foobar-reagent {} ($ foobar-uix {})])]

juhoteperi 2024-11-11T19:55:30.583089Z

$ just returns React elements, so you can place those into Reagent Hiccup. To use Reagent inside UIx, you need to call as-element to convert the Hiccup to React elements

Macroz 2024-11-11T19:55:37.261589Z

I prefer my atoms to hooks and Hiccup to $ 🙂

❤️ 2
juhoteperi 2024-11-11T20:10:58.429969Z

There is still some chance it is possible to just remove the Reagent ratom/rendering queue, and Reagent would work without it and with React queue pretty close to how it works now. Maybe the change itself is small, going through the test suite to get tests passing might be more trouble.

Roman Liutikov 2024-11-11T20:12:43.898479Z

I think one way to keep using Reagent with newer React is to transition to hooks for local state and use-subscribe to interop with reframe

juhoteperi 2024-11-11T20:13:15.993529Z

So just no atoms?

juhoteperi 2024-11-11T20:14:01.638609Z

That should indeed work. Use-subscribe will handle re-renders through React without Reagent so even the inputs should work with that.

Roman Liutikov 2024-11-11T20:14:05.308859Z

I think so, since ratoms are hooked into Reagent’s queue, but use-state is not

juhoteperi 2024-11-11T20:15:51.231859Z

Ratoms also trigger re-render for :f> components using a state hook. IF we remove Reagent queue and make functional components (`:f>`) the only option, Reagent should work pretty close to how state hook change queue works.

Roman Liutikov 2024-11-11T20:16:54.432389Z

Additionally, Reagent could have a new version of ratoms baked by use-state, this should minimise the amount of work needed to support React 18 from Reagent users

juhoteperi 2024-11-11T20:17:49.267299Z

Currently it is something like, Ratom watch places a update into Reagent queue, RAF handler takes that update from queue and calls useState hook to trigger functional component re-render, and React places this hook change into a queue.

juhoteperi 2024-11-11T20:18:11.246449Z

Maybe. I've considered it, but rules of hook use are quite different to where ratoms are allowed to be used currently.

1
juhoteperi 2024-11-11T20:18:52.904289Z

You can deref ratom in a loop, or in a if block only conditionally etc.

Roman Liutikov 2024-11-11T20:22:40.045399Z

deref is just reading a value, so it’s not like it’s calling actual hook

Roman Liutikov 2024-11-11T20:24:06.699029Z

Also form-2 components already require to create local state at top level, this kind of aligns with the rules of hooks

👍 1
2025-02-05T01:53:25.809729Z

@juhoteperi > It is possible that the React batching/queue would take care of "de-bouncing" de-references in render functions. LIke @foo-atom. So if atom changes multiple times (in a short window), component is rendered only once. Not sure about this. Yes, I can almost confirm React 18 will render only once for multiple ratom derefs in one frame, since it performs automatic batching. But I'm worried about the Clojure render function being re-executed innecessarily multiple times (i.e. generating new hiccup for each ratom reset!).

2025-02-05T01:57:45.334859Z

@vincent.cantin Not sure if this thread is the right place, but I'll share the details, as said before, I wanted to experiment with it. I changed batch/queue-render calls to calls of:

(defn immediate-render [comp]
  (when comp
    (.forceUpdate comp)))
The results where the same for every context, just ran run-tests.sh
##
## SUMMARY
##

FAIL browser-cljsjs
FAIL browser-cljsjs-prod
FAIL browser-npm
FAIL browser-npm-prod
FAIL bundle
FAIL bundle-adv
OK   node-npm
FAIL shadow-cljs-prod
And the failed tests are about ratoms running more than once, I checked the code and what is happening is that the render function is re-executed on each ratom reset! or swap! but maybe this is actually expected?
+ test -f target/shadow-cljs/resources/public/js/karma.js
+ npx karma start test-environments/shadow-cljs-prod/karma.conf.js --single-run
04 02 2025 19:43:44.838:INFO [karma-server]: Karma v6.4.4 server started at 
04 02 2025 19:43:44.839:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
04 02 2025 19:43:44.842:INFO [launcher]: Starting browser ChromeHeadless
04 02 2025 19:43:45.275:INFO [Chrome Headless 132.0.0.0 (Linux x86_64)]: Connected on socket ZdpNq-M-AUoWt6I2AAAB with id 30280693
LOG: 'Testing reagent.impl.template-test'
LOG: 'Testing reagent.impl.util-test'
LOG: 'Testing reagenttest.testcursor'
LOG: 'Testing reagenttest.testratom'
LOG: 'Testing reagenttest.testratomasync'
LOG: 'Testing reagenttest.testreagent'
Chrome Headless 132.0.0.0 (Linux x86_64) reagenttest.testreagent test-ratom-change--fn FAILED
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:103:13)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
         message: ran once more
        
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:104:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:108:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:109:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:111:13)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
         message: did not run
        
        FAIL in   (test-ratom-change--fn) (reagenttest/testreagent.cljs:114:11)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
        
Chrome Headless 132.0.0.0 (Linux x86_64) reagenttest.testreagent test-ratom-change--class FAILED
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:103:13)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
         message: ran once more
        
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:104:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:108:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:109:13)
        expected: (=
                    2
                    @reaction-ran)
          actual: (=
                    2
                    4)
            diff: - 2
                  + 4
        
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:111:13)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
         message: did not run
        
        FAIL in   (test-ratom-change--class) (reagenttest/testreagent.cljs:114:11)
        expected: (=
                    2
                    @ran)
          actual: (=
                    2
                    9)
            diff: - 2
                  + 9
        
Chrome Headless 132.0.0.0 (Linux x86_64) reagenttest.testreagent batched-update-test--fn FAILED
        FAIL in   (batched-update-test--fn) (reagenttest/testreagent.cljs:134:13)
        expected: (=
                    5
                    @ran)
          actual: (=
                    5
                    6)
            diff: - 5
                  + 6
        
Chrome Headless 132.0.0.0 (Linux x86_64) reagenttest.testreagent batched-update-test--class FAILED
        FAIL in   (batched-update-test--class) (reagenttest/testreagent.cljs:134:13)
        expected: (=
                    5
                    @ran)
          actual: (=
                    5
                    6)
            diff: - 5
                  + 6
        
LOG: 'Testing reagenttest.testtrack'
LOG: 'Testing reagenttest.testwithlet'
LOG: 'Testing reagenttest.testwrap'
Chrome Headless 132.0.0.0 (Linux x86_64): Executed 194 of 194 (4 FAILED) (2.751 secs / 2.721 secs)
TOTAL: 4 FAILED, 190 SUCCESS

FAIL shadow-cljs-prod

Roman Liutikov 2025-02-05T08:55:01.357219Z

> I can almost confirm React 18 will render only once for multiple ratom derefs in one frame, since it performs automatic batching. Correct, since v18 React batches all updates. Which still means a single render function execution per batch.

👍 1
2025-02-03T02:33:27.022849Z

Hi @juhoteperi I'm very interested in this topic and just wanted to understand it a bit further. If the reagent queue is removed, does it mean that every ratom reset! will re-run the subscribed (`@`) component's render function? I mean, if we have 3 calls to reset!, the render function of a component subscribed will run 3 times, but React will queue these 3 state updates and actually perform the rerendering only one time (if everything happens in 1 frame)? I've experimented by just removing the render queue and directly calling .forceUpdate, and found something interesting: • Tested a small app and seemed to work exactly as before (it uses both functional and class components, ratoms and hooks). • Ran small tests to measure the re-rendering time when using ratoms in my machine, and found that with the queue removed, the component re-rendered about 2ms after a state change, compared to using the queue that was around 16ms (pretty obvious, because afaik it uses requestAnimationFrame or similar) • Ran Regent's tests and in every context but NPM, 4 tests failed. NPM successfully passed all tests. And the failing tests were always about the render function being executed more than expected (`9` times instead of 4, 6 times instead of 5, and so on), other tests always passed. So I was wondering, what are we expecting to happen when we want to remove the render queue? Also wanted to ask for details on the difficulties of supporting React 18, if we change the way we render the root, what is broken or not working? Maybe these details can be in the reagent repo? (or are they already there?) So that the community might take a look and contribute to solve it. In the recent state of cljs survey results shared (with low participation IMO), reagent is used by more than 50%, so definitely there's interest on it. Thank you!

👍 1
2025-02-03T06:26:40.655489Z

@ulises.ssb506 I am also interested to know what part are failing. Which tests failed? Were the tests run on the release build or the dev build?

juhoteperi 2024-12-02T08:11:08.303399Z

Status: I've merged the updated test utils and test suite changes to master: https://github.com/reagent-project/reagent/pull/613/files React 18 branch is rebased against those changes so the diff is quite small and component rendering tests are enabled and most are OK: https://github.com/reagent-project/reagent/pull/612 No progress on figuring out what to do with Ratoms (reactions etc.), is it OK to just remove queue from those / how to fix the tests / should or is it even possible to keep the queue just for reactions.

👍 4
🙏 1
juhoteperi 2025-02-03T08:34:38.421659Z

The very short version: It is possible that the React batching/queue would take care of "de-bouncing" de-references in render functions. LIke @foo-atom. So if atom changes multiple times (in a short window), component is rendered only once. Not sure about this. But this doesn't help with reactions (including re-frame subscriptions), which currently use the Reagent queue. If these start running each time any input changes, it can be very costly because Re-frame code is now heavily dependant that subscription with multiple inputs doesn't run on every change. And if we still keep a queue just for these, then the input workaround would still be needed...

Roman Liutikov 2025-02-03T10:05:27.960309Z

I think keeping re-frame with its own queue should be fine. For React it’s an external source of data and it doesn’t really care how the state is supplied.

2025-02-03T10:06:59.334399Z

Yes. Re-frame doesn't need to change its implementation, the users can just use rf/dispatch-sync on the controlled components.

juhoteperi 2025-02-03T10:08:48.997889Z

Re-frame has its own queue for events, which can be skipped by using dispatch-sync, but the subscription side just uses reactions (= reagent queue).

Roman Liutikov 2025-02-03T10:33:05.822789Z

I’d say it should be possible to use async dispatch as long as Reagent’s input is injected

Roman Liutikov 2025-02-03T10:33:42.137939Z

Unless you want to get rid of it of course, but for compatibility reasons it might be fine since existing projects already use it

2025-02-03T10:37:11.799369Z

Using an async rf/dispatch in the :onChange callback of :input elements may create a different problem, similar to a larsen effect, when the end user types fast in it.

2025-02-03T10:38:02.588979Z

But it's a problem that already exists, so not a break in compatibility.

👍 1
juhoteperi 2024-11-19T08:12:49.306209Z

I did start trying out what would happen if we remove the Reagent ratom/render queue: https://github.com/reagent-project/reagent/pull/612/files I've mostly solved the test suite problem with React 18 rendering being async, with a new test utils that can use React.act to trigger side-effects and wait for React to render those changes into DOM. Nearly all tests rendering into DOM are now using that and also passing. Ratoms themselves kind of broken, or working differently. Removing the queue has a big impact on how reactions are running because each ratom change (reset! or reaction getting a new value) triggers the watchers instantly. For components this isn't a problem, as React will only render the component once even if the ratom watcher triggers the component update multiple times. For reactions this could be a problem. There are also some differences where render fn does some side-effects that cause new render, being triggered too often. I will likely try to adapt the test utils and test suite updates to the current version next, as they will make the test code better, and make the diff to this version smaller.

👏 2
🦾 2
🙌 8
juhoteperi 2025-01-31T08:27:57.574379Z

No updates on getting Reagent working without Reagent render queue. But I've done some Github cleanup and closed bunch of old PRs and issues. Open issues fit into one page now! I've fixed some small things, and mostly just closed tickets that don't make sense now, have been fixed years ago or something. Most interesting update might be that I documented kind of a security issue or consideration with Reagents design using Hiccup data to construct React elements: https://github.com/reagent-project/reagent/blob/master/doc/Security.md (I've previously mentioned this issue in somewhere on discussions on why I now prefer $ macro on UIx and some other solutions other libs use.) I do have a PR to limit the effect by requiring dangerouslySetInnerHTML property to use special tagged values: https://github.com/reagent-project/reagent/pull/621

👍 4
2