This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-25
Channels
- # announcements (16)
- # babashka (110)
- # babashka-sci-dev (11)
- # beginners (50)
- # biff (3)
- # calva (1)
- # clj-commons (19)
- # clj-kondo (1)
- # clojure (17)
- # clojure-art (19)
- # clojure-austin (5)
- # clojure-berlin (2)
- # clojure-denmark (3)
- # clojure-europe (101)
- # clojurescript (84)
- # clr (1)
- # core-async (2)
- # emacs (3)
- # helix (5)
- # honeysql (4)
- # hyperfiddle (8)
- # introduce-yourself (2)
- # jobs (1)
- # lsp (18)
- # membrane (3)
- # reagent (5)
- # releases (3)
- # shadow-cljs (10)
- # tools-deps (24)
I used previously react-table
version 7 with Clojurescript - it was relatively straightforward when using Javascript data structures interacting with react-table. Now there is a new version https://tanstack.com/table/v8 - and the examples are with Typescript. I'm wondering if it is possible to use this new version 8 with Clojurescript?
E.g. with Typescript:
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
export type ProductGroupType = {
pgId: number;
name: string;
};
const pgColumnHelper = createColumnHelper<ProductGroupType>();
const columns = [
pgColumnHelper.accessor("pgId", {
header: "Id",
cell: (info) => (
<NavLink to={`/products/` + info.getValue()}>{info.getValue()}</NavLink>
),
}),
pgColumnHelper.accessor("name", {
cell: (info) => info.getValue(),
header: () => "Name",
}),
];
...
I'm a bit puzzled how to convert that to Clojurescript.most sane typescript libraries will distribute themselves as minified javascript — typically ESM (but CJS sometimes exists too)
basically, if a TS library can be used from vanilla JS, it can be used from CLJS — and to follow TS examples, you can get away by just ignoring type declarations… Problems arise when they start using TS-specific stuff like abstract classes
I'm not that fluent with Javascript. I was wondering, how to define e.g. the above type ProductGroupType
in Javascript...
But this is an interesting exercise for me! 🙂
Ah, ok. 🙂
No type decls -> inferred as any
type -> typed functions wanting a specific type like T
will cause a TS compiler error since any
is not as specific as T
that’s why in react typescript codebases you’ll see lots of boilerplate like defining the types of each prop of a component inside an interface then doing sth like function component(props: componentProps) { … }
Ok. I try, let's see how it works out! 🙂
Sometimes types are important in JS as well. Interop to create them is less than nice, but if you use shadow-cljs then there's shadow.cljs.modern/defclass
.
Ok. Let's see if I'm able to do this. 🙂
I almost did it. 🙂
I just couldn't make the hyperlink work in the pgId
column. And I just can't figure out why it is not working.
In Typescript I had:
const pgColumnHelper = createColumnHelper<ProductGroupType>();
const columns = [
pgColumnHelper.accessor("pgId", {
header: "Id",
cell: (info) => (
<NavLink to={`/products/` + info.getValue()}>{info.getValue()}</NavLink>
),
}),
...
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
In Clojurescript I thought this would do the trick:
(defn mylink
[pg-id]
[:<>
[:a {:href (rfe/href ::f-state/products {:pgid pg-id})} pg-id]])
...
columnHelper (rt/createColumnHelper)
columns #js [(.accessor columnHelper "pgId" #js {:header "Id" :cell (fn [info] [mylink (.getValue info)])})
...
[:tbody
(for [^js row (.-rows (.getRowModel table))]
[:tr {:key (.-id row)}
(for [^js cell (.getVisibleCells row)]
[:td {:key (.-id cell)}
(rt/flexRender (.. cell -column -columnDef -cell) (.getContext cell))])])]]]))
But the hyperlink just won't be there (just the cell value).I imagine the :cell
function must return a React element instead of a Hiccup vector. You probably have a bunch of errors in the JS console.
If so, you can try wrapping that [mylink ...]
in (reagent.core/as-element ...)
.
Nope, no errors in the JS console.
But let's try that...
Gosh! Thank you!
Now I really want to understand this. Why do I need the as-element
here?
Let's read documentation.
I tried to figure this out some 2-3 hours. I should have asked help here 2 hours ago. @U2FRKM4TW: Thank you so much! ❤️
To give back to the community, I wrote a blog post about this exercise. Hopefully someone can benefit from it: https://www.karimarttila.fi/clojurescript/2023/02/28/clojurescript-javascript-interop-with-react-component.html
Hey guys. Can CLJS export ES modules rather than CJS requires? I only see the default --target
and --target nodejs
(which is what I'm using at the moment).
I have some code in CLJS, but the tests are in JS (so they can work as a reference for other people like the ones who don't know CLJS). I'd like the tests to run on Deno.js.
I'm aware there's @borkdude's bebo, but that seems to be geared towards running CLJS in Deno, while I need to consume CLJS from JS (hence compilation is probably necessary).
@U024VBA4FD5 Check out the latest messages in #nbb: nbb is now able to run in Deno (although this isn't what you asked for)
Nbb itself is built as an ESM module so you can use it from JS:
import { loadString } from 'nbb'
await loadString(`(+ 1 2 3)`);
So you could look at its shadow-cljs.edn
file to see how it's set upReally appreciated @borkdude! I think cherry is what I'm after.
How the hell do you have time for all these projects? Seriously...
Note that both squint and cherry are still experimental, so shadow-cljs + target esm I recommend as the safe option ;)
What I have is pretty basic, just some numeric functions to calculate business projections...
You can also do this with nbb btw.
Just use loadString
from the JS test file and from the .cljs
file do:
#js {:my-fn my-fn}
so:
const { my-fn } = await loadString("my_file.cljs")
That'd be the best, but I have a file full of (CLJS) fns and I need to import these (so I can test them).
That's perfect.
Does anyone have any guidance for producing a PWA with shadow-cljs (or without it if that’s better)?
I have simple pwa project: https://github.com/nenadalm/life-counter/tree/f40732323d402ac28c408003276be8af2d860f0d The worker is written in js: https://github.com/nenadalm/life-counter/blob/f40732323d402ac28c408003276be8af2d860f0d/resources/private/worker.js - the js is kinf of a template with files replaced in: https://github.com/nenadalm/life-counter/blob/f40732323d402ac28c408003276be8af2d860f0d/src/build/create_worker.clj#L24 Guide for js is on mdn: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps
Hey everybody. Very long time since I've been here—great to be back. 🙂 I have a question about how people are using React from ClojureScript these days. I saw https://www.youtube.com/watch?v=3HxVMGaiZbc&ab_channel=ChariotSolutions by @dnolen where he describes a workflow of writing React component functions directly in JavaScript, then using ClojureScript for all the business logic. That sounds ideal, but I'm hung up on what that actually looks like. Can anyone hazard a guess? For instance: is there one top-level component function defined in ClojureScript which instruments the JS components? Is the app state defined in ClojureScript and passed into JavaScript? How does one handle passing and accessing Clojure data to and from JavaScript? Just kind of looking for a sketch to follow if anyone's done this.
Ah, searched around this channel and looks like it's been discussed a few times. It sounds like: 1. Top-level is instrumented using Reframe/Reagent 2. App state is obviously Clojure data structures 3. Reagent is handling the JS <-> CLJ data structure conversion, for the most part. Is that basically right?
That is one way of using React in CLJS, yeah. You can make functions returning vectors encoding HTML data -- this is often called "Hiccup". Re-frame is in charge of state management. Reagent is in charge of rendering your Hiccup into a React component
Thanks. I think Ill try to build a toy version of that and post it as an example. Im mostly just fuzzy on the boundary between CLJS and JS from David's description. The hard thing about not having done this in a while is that it feels like there's a large number of choices about how to layer over React, and it's not obvious what the trade-offs are.
There are roughly three choices: • Om/rum/etc. are legacy wrappers and not really in active development anymore • Reagent is the most popular choice, but it is very awkward to use with modern React features (hooks, suspense, etc.) and there is a runtime cost to interpreting Hiccup • Helix is a thin React wrapper designed specifically to play nice with JS interop and modern React features; unlike Reagent, Hiccup is replaced with a macro that attempts outputting as much components as it can at compile-time. This in theory should make Helix outperform Reagent in performance.
Considering the new React documentation is written entirely with function components and hooks and class components are explicitly labelled "Legacy" and "not recommended" for modern projects. I think your best choice is to use Helix, since class-based components are effectively deprecated in React
@U0479UCF48H Thanks - I had been wondering about the intersection of hooks and Reagent/Reframe. It kind of seems like a problem since it'd be hard to play with the rest of the ecosystem, like react-use.
@borkdude 👋 Thanks! Great to see some familiar faces still here.
@U0479UCF48H Do you know if there's an opinion in the Reagent/Reframe communities regarding what to do about the challenges with hooks/etc?
As far as I’ve been able to tell — the overall approach seems to be “ride out class components until they’re finally ripped out of React”
how long that time will come? I’m not sure. But even when it does come, the last major release of React with class components will likely be good for an extra 1-2 years. And most people in the Clojure community are fine with staying on older libraries provided they still work for their intended use case. That is to say, people here seem to prefer stability over novelty
e.g. many react libraries out there still support React 16 (the latest version is currently 18)… so it is not the end of the world once a major release of React drops class based components entirely
Thanks - very valuable information. It's been hard to gauge how concerned to be about what seems like volatility. That helps.
No problem! And do note that reagent does allow emitting function components in case hooks are absolutely necessary and your project has decided to use reagent
Ah, that's fantastic. That makes me feel a lot better about Reframe then. Absolutely the right abstraction, hopefully reasonably future proof. 🤞
(I guess Im assuming that carries up from Reagent)
Yeah. Re-frame depends on reagent, but reagent does allow support for function components and hooks. The caveat being that you must denote them explicitly with :f>
like below.
(defn my-component []
(let [[count set-count] (react/useState 0)]
[:div
[:p "Count value: " count]
[:button {:on-click #(set-count inc)} "Increase count"]]))
(defn my-page []
[:div
[:h1 "My function component"]
[:f> my-component]])
(warning: did not test this code, but it is a basic example of how to use hooks and emit function components in reagent)
I’m personally keeping a close eye on Helix mostly for two things: (1) performance, and (2) being able to embrace modern React from the ground-up
I don’t think state management libraries for Helix are “quite there yet”, however. But refx is super promising since it seems to be API compatible with re-frame (in fact, it is an implementation of re-frame without reagent)
So the key takeaway here is: you should be able to use re-frame for many years, just know the situation upstream about class components and function component
Whew, you answered heaps of questions in 5 minutes that I've spent all last week googling. Hugely appreciated 🙂
Also add to this that you can use #uix (https://github.com/roman01la/uix orhttps://github.com/pitch-io/uix) which are thin wrappers over React for ClojureScript. This let's you use Modern react hooks in clojurescript and also makes it very easy to interoperate with the whole React ecosystem.
Also worth noting that server side rendered clojure apps are now extremely powerful using a combo of hiccup
, compojure
, https://github.com/bigskysoftware/htmx and https://github.com/bigskysoftware/_hyperscript. Allowing you to build fast and interactive webapps entirely in Clojure on the serverside without clojurescript. This has several benefits including unified codebase, no API needs to be built and better SEO.
Out of curiosity, what does Uix do differently from Helix? (I think I saw Uix mentioned in the refx library docs a few days ago, but I never looked it up)
UIX and Helix have some similarities (especially Helix and UIX V2). UIX V1 heavily used hiccup datastructures so you could define a large part of the UI in config (e.g. EDN) and then have a small amount of code just for loading the UI and injecting hooks. So I think it's a case of checking out both Helix and UIX and seeing which you like better from a design/usability/etc perspective.
@U050A65BL I don’t think the wrapper is very important here to be honest wrt. idea
basically we just write components in JS, then we “script” them in Reagent as if they were written in ClojureScript - you don’t really care that much.
The only real issue is that nested JS components won’t be able to take EDN props, so we use context to thread in a converter which the JS components can use to get at the props.
When you say "script", you basically mean you have a mirror set of views in CLJS that just call through to JS, right? > The only real issue is that nested JS components won’t be able to take EDN props Great, that is exactly the piece I was hung up on. Thanks for sharing!
Reagent has a function for taking any React component and making them usable in hiccup, that’s all we do
originally the main thing here was Storybook - there is a Storybook narrative for CLJS now
but for us not a big deal since by writing components in JS it’s easier to onboard JS folks
and the benefits of ClojureScript when you’re writing purely functional components w/ no business logic is not particularly significant
wiring up the app is course very custom, and we’re getting data from a Clojure service, so we’re dividing up the effort into the parts where’s there some obvious benefit
ClojureScript + JavaScript + HTML can play really well together. Agree that hybrid frontend codebases can work well and you don't have to turn everything into ClojureScript.
I've found that with just a few react hooks (`state`, effect
,`callback` and memo
+ a few others) you can build many types of UI component. So for simple components we often barely interface with react at all. So we use clojurescript + UIX which gives us idiomatic access to React hooks + hiccup which is great for expressing HTML.
@dnolen Thanks, very helpful description. It's been a long time since I've worked in this area, so having a bit more color on how exactly it works helps. If I get it working, Ill pay it forward with a little blog post. 🙂
@UJVEQPAKS Yeah after I had dabbled around with plain React for a few weeks, it felt like a mistake to ignore hooks. Everything's centered on them now for better or worse.
I would also mention hicada. It's pretty much a macro instead of JSX that rewrites hiccup into react.createElement at compile time. The wrapper is very thin and allows you to use everything react has to offer. I believe it's the best way if you plan to use a lot of 3rd party react components libraries or complex components like editors etc.
@U4EFBUCUE Thanks! Ill check it out!