react

"mulling over state management stuff"
orestis 2020-04-25T09:27:35.001800Z

Podcast discussion about redux that’s interesting: https://overcast.fm/+IoAqtBpJU - hope to finish it soon.

dominicm 2020-04-25T09:33:45.002500Z

https://stackoverflow.com/a/36657179

dominicm 2020-04-25T09:34:07.003300Z

There's a lot of complaints about redux around boilerplate and complexity. So we should aim to avoid those somehow.

dominicm 2020-04-25T09:35:20.004300Z

I think https://github.com/riverford/compound would solve many of the issues people have with building up various indexes for their state.

dominicm 2020-04-25T09:36:25.005600Z

I think by keeping out of the ui layer, we solve many of the re-xxx problems.

dominicm 2020-04-25T09:37:48.007100Z

We will need to decide what our http solution is. Probably either promises or core async. While I'm reluctant to go heavy with core.async, libraries like cljs-http will integrate better out of the box that way.

orestis 2020-04-25T09:43:06.007500Z

What’s wrong with good old XHR?

orestis 2020-04-25T09:44:24.009200Z

I’ve stayed away from core async for sometime, esp. on the front end. The go macro seems a bit scary to me, not sure how it would look like in things like React dev tools.

dominicm 2020-04-25T09:55:47.010500Z

js/fetch is okay, I like it. Cljs http was surprisingly short when I looked. It's mostly about content negotiation I suppose.

dominicm 2020-04-25T09:56:10.011200Z

Core async wouldn't show up I don't think, as it operates at a different level.

dominicm 2020-04-25T09:58:08.012100Z

A callback interface is good too, I think people quite enjoy playing with core async though.

dominicm 2020-04-25T09:58:30.012200Z

Callback hell is probably less relevant in this context though

orestis 2020-04-25T10:14:26.012600Z

Ah, the joy of not having to support IE11 :)

😭 1
orestis 2020-04-25T10:19:22.013200Z

I wanted to follow down https://mobile.twitter.com/dan_abramov/status/1246251834324516868 for some time. Cancellable XHR wasn’t on my radar.

orestis 2020-04-25T10:21:20.014100Z

In any case, http is async, so handling async events is the general case.

Aron 2020-04-25T10:30:34.014500Z

I also have to support ie11 at work, it's not joyous at all : ( : D

orestis 2020-04-25T10:45:30.015900Z

http://Polyfill.io saves some time. We took up bootstrap 4 for a css baseline but it’s still a pain.

dominicm 2020-04-25T13:19:11.017Z

Yeah, core async isn't too natural here... You can close the channels but if you have many that won't be pretty

dominicm 2020-04-25T13:19:31.017600Z

Cancel friendly async seems like a difficult challenge

Aron 2020-04-25T15:10:48.018900Z

I tried polyfill io but it wasn't reliable. Since then we bundle the necessary polyfills and serve them with our app

lilactown 2020-04-25T17:20:11.021800Z

I like what someone on twitter said recently... that when thinking about async processes, one should start with cancellation, since it's often ignored until later and really mucks with a lot of seemingly nice abstractions that model things like a sync pipeline

1
lilactown 2020-04-25T17:22:44.022600Z

core.async you basically need a succeed/failure/cancel channel. using an event system like re-frame/redux is also complicated, see https://github.com/davidkpiano/useEffectReducer/issues/1

lilactown 2020-04-25T17:22:55.023Z

there's lots of tradeoffs

lilactown 2020-04-25T17:26:10.024300Z

https://github.com/davidkpiano/useEffectReducer/pull/8 I think davidkpiano took the route I've been thinking of where you keep a reference to a process object and use that to control cancellation/cleanup, but it's sort of a side channel to the event-driven system you have before. it doesn't quite feel first class.

👍 1
lilactown 2020-04-25T17:31:00.027400Z

a sort of subtle thing that I'm not sure of in useEffectReducer is when the cancellation occurs - or SHOULD occur: • immediately as part of the event (e.g. click away, receive response from something else) - immediate cancellation, but render in relation to that might not be scheduled until later • during the scheduled render - second fastest, but might be called multiple times due to renders being retried due to suspensions • during use(Layout)Effect - safest in CM but maybe too late if you're coordinating cancellation with processes outside of the react tree??

lilactown 2020-04-25T17:32:03.028100Z

i could see a network request being cancelled immediately on user interaction, but for animations you would want to maybe wait until flushing to the DOM?? I'm not sure

lilactown 2020-04-25T17:49:22.028600Z

I've decided that the first problem I want to solve for this data fetching malarkey is normalization

lilactown 2020-04-25T17:49:39.028900Z

it's the one that I can sort of understand the best (also it sounds fun)

lilactown 2020-04-25T17:50:42.029900Z

fulcro, datascript, apollo, relay, etc. all have this custom machinery they've built to normalize their internal structure so that when you receive an update to an entity, all subscriptions see that update.

❤️ 1
lilactown 2020-04-25T17:51:26.030700Z

I want something more a la carte: a map-alike data type that auto-normalizes data given to it

aisamu 2020-04-25T17:54:39.030900Z

Isn't something like https://github.com/stuartsierra/mapgraph sufficient?

lilactown 2020-04-25T17:56:33.031600Z

haha, i figured there's probably prior art here but couldn't find exactly what I wanted. that looks p much what I'm building

aisamu 2020-04-25T18:03:28.034300Z

https://github.com/den1k/subgraph Extends that to CLJS (cljc), adds recursive join queries and has an optional re-frame layer

lilactown 2020-04-25T18:04:28.035100Z

there's a couple of features that I would personally like to have that aren't currently present in mapgraph: 1. Entities can have many lookup refs, e.g. [:account/id 123] and [:account.contact/email "foo@bar"] could both be unique ways of referring to the same entity 2. separate schema from db, have foreign keys and join dbs

lilactown 2020-04-25T18:05:06.035400Z

interesting that I found subgraph but didn't find mapgraph in my search

aisamu 2020-04-25T18:05:58.036500Z

Yup, I learnt about MapGraph on SubGraph's docs

lilactown 2020-04-25T18:07:12.037500Z

I also think I could miss the recursive specific query bits, all I really want out of this is something that I can shove some data into and then get it back out fully hydrated

lilactown 2020-04-25T18:07:40.037900Z

but having the mapgraph impl as a reference is very helpful

orestis 2020-04-25T18:08:22.038200Z

What about this https://github.com/keechma/entitydb

aisamu 2020-04-25T18:12:26.040700Z

When I evaluated both, I found entitydb to have more ceremony around schemas. Subgraphs/(mapgraph?) considers every map as a possible entity, but only normalizes those containing "marker"/id keys

lilactown 2020-04-25T18:13:28.041700Z

I looked at entitydb and decided I wanted a more map-alike API (get/assoc/dissoc/etc.) but maybe that's foolish

aisamu 2020-04-25T18:14:05.042300Z

(More ceremony doesn't mean the schema is not useful - but the team found even subgraph's approach overwhelming)

lilactown 2020-04-25T18:22:28.049100Z

I like entitydb's idea of declaring schema, but I'd prefer to do something like:

(def schema {:person/id {:db/unique :db.unique/identity}})

;; create a new map with associated schema
(def entities (entity-map/empty schema))

(-> entities
    (assoc
     {:person/id 123
      :person/name "Will"
      :friends [{:person/id 456
                 :person/name "Mallory"}]))
;; => {#{[:person/id 123]} {:person/id 123
;;                          :person/name "Will"
;;                          :friends [[:person/id 456]]}
;;     #{[:person/id 456]} {:person/id 456
;;                          :person/name "Mallory"}]}

👌 1
lilactown 2020-04-25T18:25:15.050Z

I dunno, I'm having fun. y'all should probably use entitydb, it actually exists and has tests 😂

lilactown 2020-04-25T18:25:40.050200Z

or mapgraph

aisamu 2020-04-25T18:27:17.050300Z

What's the reasoning behind the sets as keys to the entities? An issue I had with subgraph (compared to fulcro, for example) is that the store had the full ident as the keys instead of just the values:

{:user/by-id {[:user/id 10] {:user/id 10 :user/name "me"}}}
vs

{:user/by-id {10 {:user/id 10 :user/name "me"}}}
It's sounds silly, but inspecting the former on the browser console was a nightmare even with cljs-devtools

orestis 2020-04-25T18:30:04.052100Z

assoc looks iffy to me. Suggests a plain map underneath. Stratified design would suggest more of an intentful api.

lilactown 2020-04-25T18:30:55.052600Z

yeah I think the case I gave above actually would need to use something else (merge or probably a special add fn like mapgraph)

lilactown 2020-04-25T18:32:16.053600Z

the reasoning is to support multiple idents pointing to the same entity

1
lilactown 2020-04-25T18:35:20.054600Z

but assoc should still work:

(assoc-in entities [(entity-map/ident :person/id 123) :friends] [{:person/id 456 :person/name "Mallory"}])
;; => returns the same as above

lilactown 2020-04-25T18:36:13.054700Z

there's probably a better way of printing it, you're right, any big complex map like that gets pretty unwieldy to read

lilactown 2020-04-25T18:49:19.055500Z

I wonder how fulcro allows {:person/id {123 ,,,}} structure while allowing multiple idents to refer to the same entity

orestis 2020-04-25T18:53:49.058700Z

I think the trick is to shift from a map interface to a db interface. Maps etc are implementation details, there’s probably a few different data structures needed to power this under the hood. (I’m talking about the assoc/assoc-in API you showed above)

orestis 2020-04-25T18:54:47.060Z

Might be just me, lots of times Clojure makes it so easy to keep using the “low level” stuff that I never consider a more apt API.

dominicm 2020-04-25T18:57:46.060300Z

https://github.com/riverford/compound feels very lightweight

➕ 2
dominicm 2020-04-25T19:01:46.061500Z

90% of my annoyance with just using a normal map is filter/first and jumping indexes.

dominicm 2020-04-25T19:03:50.064400Z

If I just had a map-like data structure I could do, (conj db {:name "Fred"}) and (get-in db [:name "Fred"]). Create indexes on demand

Aron 2020-04-25T19:04:57.064900Z

This is a WIP thing I made more than a year ago in js https://gist.github.com/ashnur/f2fb2cf230d47aea9a63123aedb5926a

Aron 2020-04-25T19:06:24.066Z

it has something like the Elm architecture, two ways to send a message, one for pure stuff and one for sideeffect, but since I didn't have the Elm kind of type theoretic tools to test, needed more brutish solutions

Aron 2020-04-25T19:06:59.066600Z

I don't quite understand why it is a problem that datascript optimizes stuff internally, I quite liked this about it

orestis 2020-04-25T19:11:00.069800Z

For graphql data, just a map usually doesn’t cut it. At least, that’s the whole raison d etre for Apollo. Now I’m very suspicious of that library and the actual gains you have by caching that aggressively, but I guess there must be a valid application.

Aron 2020-04-25T19:23:32.072200Z

Has anyone here used graphql in practice for anything serious? I tried once with a java/hybernate db backend that didn't really worked out as well as I hoped, and I always expected some way to use it for local data too

lilactown 2020-04-25T19:24:06.072500Z

we used graphql heavily at my last job

lilactown 2020-04-25T19:24:29.073100Z

I thought it worked well for the front end using apollo, but it was quite a bit of work on the back-end

1
dominicm 2020-04-25T19:34:12.074600Z

My problem with caching is that I'm usually pretty imperative. I, human, know when I want to fetch something for the most part.

lilactown 2020-04-25T19:46:46.074900Z

the key thing I've realized is the need to separate fetching from reading

lilactown 2020-04-25T19:47:12.075300Z

typically you start fetching on some user action, but you read while rendering

Aron 2020-04-25T19:47:48.076100Z

or rather, you render when you read 🙂

lilactown 2020-04-25T19:48:10.076500Z

due to CM/Suspense there's not a synchronous flow between completing fetch and rendering, so you need a cache

Aron 2020-04-25T19:49:02.076900Z

but this cache for me was datascript. I fetched into it, and then I rendered consistent entities from it

Aron 2020-04-25T19:49:15.077200Z

because of the schema, I could just pass in json objects from javascript

dominicm 2020-04-25T19:50:00.077800Z

Caches are the hardest problem though

lilactown 2020-04-25T19:52:42.079500Z

yeah I'd really like to use datascript but there are tradeoffs. there is a relationship between fetch and query that is hard to create with datascript. that's where GraphQL shines, is that the request send to fetch from the back-end is easily relatable to the query on the cache for what you need to render

Aron 2020-04-25T20:11:48.081100Z

I would say the two are not exclusionary, if someone wants to use both.

lilactown 2020-04-25T21:10:05.082200Z

yeah maybe a good canned set of libs would be: • send queries as EQL with pathom on the backend • use datascript as front-end cache

dominicm 2020-04-25T21:51:37.082900Z

https://github.com/noprompt/meander provides an alternative view on querying in indexed data structures

Aron 2020-04-25T23:01:17.089200Z

https://github.com/Lokeh/helix/blob/master/docs/pro-tips.md#dont-use-deep-equals so what we did plan a couple of times, and in theory I find it straightforward, but we should see 🙂, is that, every View (eg. react component) or state user (react component if it depends on remote state, or a service worker that manages a datascript instance or whatever you like to put your state in) has some kind of namespace registration (at boot time) over what it wants to read or write (this erases the difference between local and external state, in the sense that although everything is global, you can trivially encode read/write access between components so second registrar would warn/throw/be ignored and thus you have local state, that you still can load together with everything), this static query/namespace description can be any kind of query language, I of course prefer datalog, but it it doesn't matter, as long as you can replicate the queries 1-to-1 on the backend. Because this means that using some kind of deterministic hashing, you can always send the hash of the last result you got on the client, and then the backend can just look at the hashes on its own to know what do devalidate. (I am not saying this makes it easier, but it's at least a kind of straightforward way to understand it from the client side).

Aron 2020-04-25T23:24:20.098400Z

I don't think there is ever any reason to do a check like in the link though. Unless you do heave logic in the render, but why would you put heavy logic in the render? 🙂 If the react component's only work is to create Elements, the diffing of the reconciler will do the optimizing for you. Then all what is important is that when something happens that is buffered correctly. So I used channels and basically let messages drop as often as I could, so I don't feed React more than it was absolute necessary. I never had any kind of performance problem and I used datascript from javascript, with mori-js interface (the two were bundled together). And I wrote my datalog queries in javascript and generated GraphQL from them (in 2015).