react

"mulling over state management stuff"
orestis 2020-04-30T11:40:05.225700Z

Another thread about react and state management: (this time about performance) https://twitter.com/buildsghost/status/1255756139033051138

➕ 2
lilactown 2020-04-30T14:37:38.226600Z

he's been on a roll lately

orestis 2020-04-30T15:27:24.228Z

I had no clue who he is, seems like he is approaching React with a fresh mind? After 9 years of doing custom UIs in Objective-C / Mac, I feel there’s a lot of interesting ideas to bring to web dev. Not all of them practical, sure, but still interesting 😄

lilactown 2020-04-30T15:37:01.229200Z

I know he used to work at facebook, he's participated a lot in the ecosystem surrounding React (yarn/babel/flow).

lilactown 2020-04-30T15:37:38.230Z

I stopped following him a year back because he was just incredibly toxic on twitter (and got banned for it once or twice), but seems he's trying to build his brand back

lilactown 2020-04-30T15:38:18.230600Z

which I'm here for, he clearly has valuable things to share

orestis 2020-04-30T18:08:37.237100Z

An idea which I’m bouncing around my head lately -> a lot of the hand-wringing about global vs local state that I see around, is because global state is, a) well, global and potentially accessible from everywhere, and b) has its own special API (redux/re-frame/whatever). Local state on the other hand is clearly scoped and unceremonial (use a ratom/useState/setState) -> but the moment you want to share it with other components, you either need to send it to the global store or use a Context or prop-drilling. The (half-baked) idea is: what if you could have multiple stores that share the same API (via say a protocol) and could be named? The API should be lean enough to be able to replace a hook, but expandable enough to handle more advanced things (subscriptions, cursors, whatever). Then the names would carry richer semantic information -> e.g. the global-store is clearly global whereas the submission-form-store would clearly be relevant only in your submission store. I need to sit down and actually code a skeleton of this to see if it makes any practical sense (there’s some details to be figured out with Context and hooks etc), just thinking aloud here.

dominicm 2020-04-30T18:14:40.238300Z

Hmmm. For me, I like local state because it's anonymous. If I decide to mount the login form twice in the page, there's no weirdness.

orestis 2020-04-30T18:23:15.239700Z

I was more thinking like a login form probably needs to coordinate some local state with components deep down the hierarchy, right? Where you need to go into context or prop drilling.

orestis 2020-04-30T18:23:36.240300Z

I was thinking - what if local state looked like global in the API?

dominicm 2020-04-30T18:30:51.240900Z

Hmmm. Sounds like you're describing context to me, so I'm not following.

orestis 2020-04-30T18:35:44.243Z

Context is a means to pass “things” down the hierarchy without needing to thread them through components, right? A kind of dynamic scope. I’m more concerned about the thing you pass down, and how do you update it.

dominicm 2020-04-30T18:44:20.243100Z

Now I'm only thinking about:

(let [submission-form-store (react/useState {})]
  (some-child submission-form-store))
Which is probably also not right 😁 Is the key aspect that instead of (react/useState) it's something which can have cursors/minimal watchers applied?

orestis 2020-04-30T18:55:54.244700Z

Something like that - it might use hooks under the hood but you wouldn’t care. I need to do some code to see.

dominicm 2020-04-30T18:56:26.244800Z

Interested to see what you invent.

dominicm 2020-04-30T18:57:00.244900Z

@orestis it would be cool if they went into a global pool, then you could do things like (swap! (:submission-form-store @pool) inc)

orestis 2020-04-30T18:59:32.246100Z

Nice idea - kind of my thinking is to have namespace local names too. Needs more thinking.

dominicm 2020-04-30T19:03:06.246200Z

I guess I'm just ripping off https://github.com/riverford/objection there 😁

👀 1
lilactown 2020-04-30T19:16:53.246900Z

I think jamie’s opinion in the tweet above is spot on with mine

lilactown 2020-04-30T19:19:12.247800Z

use specific tools for specific behavior (forms, routing/navigation, data fetching, animation) but don’t try and align with a one size fits all approach

dominicm 2020-04-30T19:21:41.248300Z

I do wonder how much I'll care once I have fast refresh setup tbh :)

dominicm 2020-04-30T19:21:51.248400Z

but poking atoms is more fun at the REPL

dominicm 2020-04-30T19:23:03.248500Z

But I'm not sure his points on performance are correct. He says poking A, can cause unrelated B to update slowly (or maybe N things). But that's an easily solved problem. You can create the same dilemma in React by hoisting your state.

lilactown 2020-04-30T19:23:30.249100Z

yes I think there are reasons that people like global state

lilactown 2020-04-30T19:23:44.249500Z

and those reasons should be addressed, but maybe with less global state 😉

lilactown 2020-04-30T19:24:08.250100Z

like, what if you could query for a component’s state and manipulate it at the REPL?

dominicm 2020-04-30T19:24:10.250200Z

Redux doesn't use global state though, it's not exactly local state either.

dominicm 2020-04-30T19:24:39.250700Z

Redux is better than re-frame here. It scopes the store to the component tree at least.

lilactown 2020-04-30T19:25:07.251200Z

what if you had a way of getting a pic of a DAG of the way that your individual stores compose? etc.

lilactown 2020-04-30T19:26:30.251800Z

his point about Redux needing to be redesigned to participate in React’s scheduler is also spot on, and where new tools can really shine

lilactown 2020-04-30T19:28:17.252900Z

how amazing would it be to have a ClojureScript STM-like API to build on top of Reacts scheduler to avoid choking the page?

lilactown 2020-04-30T19:29:01.253600Z

if your transaction takes longer than x ms (based on priority) then pause it to take care of higher priority updates, resume it later after things quiet down

dominicm 2020-04-30T19:30:35.253700Z

I'm not sure you could do the transactions thing without a go-macro-like code rewriter to convert into a state machine

lilactown 2020-04-30T19:31:50.254100Z

I think you would just use something like alter ref-set! etc.

lilactown 2020-04-30T19:31:53.254300Z

at the low level

dominicm 2020-04-30T19:32:23.254400Z

Do you mean a long render, or a long state change? :thinking_face:

lilactown 2020-04-30T19:32:31.254600Z

a state change

lilactown 2020-04-30T19:32:53.255Z

React’s state are basically like STM refs already

lilactown 2020-04-30T19:33:24.255700Z

they don’t have “fiber-local” values but they get scheduled and committed / rolled back just like STM

lilactown 2020-04-30T19:34:03.256400Z

one of the problems Jamie was talking about was that Redux is this synchronous store that can’t participate in React’s scheduling

lilactown 2020-04-30T19:34:55.257400Z

so even though your local state updates gets scheduled according to priority of the event that triggered it, Redux updates happen immediately, always, no matter what, which can exacerbate performance problems

lilactown 2020-04-30T19:35:55.258400Z

his point is that React is smart. using it’s scheduler, it can isolate your performance problems to only the component that is slow, because it can deprioritize slow updates and let the rest of the component tree update while it waits for things to quiet down

lilactown 2020-04-30T19:37:21.259700Z

ideally, if you are going to use an external store, your calculations to update the store would also participate in this scheduling so that it could be prioritize accordingly

lilactown 2020-04-30T19:38:37.260900Z

but programming many external stores like this will twist your mind the same way that any other coordination of async updates to mutable variables will

dominicm 2020-04-30T19:39:35.261800Z

That would require the ability to pause any given body of work (e.g. a long JSON.parse...) or to predict which updates would be slow.

dominicm 2020-04-30T19:39:46.262100Z

How does React know what's slow & to give up?

lilactown 2020-04-30T19:40:03.262400Z

yeah you can only go so fine grained due to JS

lilactown 2020-04-30T19:40:26.262900Z

React yields after executing each component’s render to see if it’s taking too long

dominicm 2020-04-30T19:41:27.263Z

And if it is, it just ... waits to be slow later?

lilactown 2020-04-30T19:41:35.263200Z

yep

dominicm 2020-04-30T19:41:40.263400Z

How does it decide what to do next I guess?

dominicm 2020-04-30T19:41:52.263600Z

Seems like it's missing the priority part of the puzzle

lilactown 2020-04-30T19:41:56.263700Z

it has a queue of work

lilactown 2020-04-30T19:42:24.264200Z

I pasted a link above which I’m basically just repeating, I don’t really know how much of this works in detail

lilactown 2020-04-30T19:43:37.265500Z

AFAICT it keeps a prioritize queue of work, and after rendering each component it then checks to make sure it’s not taking too long given its priority.

lilactown 2020-04-30T19:44:19.266300Z

if there are other higher priority updates on the queue, and it’s taking too long, it pauses rendering and puts it back on the queue and takes the higher priority work

lilactown 2020-04-30T19:47:05.268100Z

for an STM-like thing, I’m not sure yet what the yield point would be… It would be good if you could yield in the middle of a transaction to see if there’s a higher priority item to process instead, but that’s not obvious how to do since you don’t have an obvious “and now I can pause and resume later”. I guess you could yield every alter or ref-set , but that would make for a very funky API

dominicm 2020-04-30T19:47:06.268200Z

OK, so we're not talking about component A vs B, we're talking about effects go last, state updates go first & go together, that kind of thing.

lilactown 2020-04-30T19:47:56.268900Z

I’m not sure exactly what you’re referencing yet. yes we’re not scheduling component A vs component B necessarily

lilactown 2020-04-30T19:48:23.269500Z

React is scheduling renders of the whole tree

lilactown 2020-04-30T19:49:05.270300Z

so if you’ve got a websocket slamming you with split-second stock data, meanwhile your user is banging on the keyboard

dominicm 2020-04-30T19:49:31.270700Z

Yeah. I'd misinterpreted the prioritization really. I thought it would be like "increment is fast, let those through, decrement is slow ignore those for a bit"

👍 1
lilactown 2020-04-30T19:49:43.270800Z

React is going to schedule a render of the tree according to the state update for every one of those events

lilactown 2020-04-30T19:50:15.271500Z

and now it can prioritize the renders that are related to the keyboard banging and deprioritize the websocket state updates

dominicm 2020-04-30T19:51:21.271700Z

Yeah. I don't see redux necessarily struggling to participate in that necessarily though :thinking_face:

dominicm 2020-04-30T19:51:54.271800Z

What we're really talking about is global prioritization. So if you can hook the scheduler, the websocket handler can schedule the redux events to be lower in priority.

dominicm 2020-04-30T19:52:46.271900Z

Seems like a poor man's version of this is to use a counter & setTimeout hooked up to global keyup events or something. Don't dispatch websockets when the keyup event counter/s is more than 3 or something.

lilactown 2020-04-30T19:57:58.273Z

yeah I think that you can get a simple strategy going pretty quickly: • make state updates async • use scheduler to prioritize and run them you at least are better than current state of the art (redux/re-frame)

lilactown 2020-04-30T19:58:28.273600Z

but getting this really dialed in comes back to that yield point question

lilactown 2020-04-30T19:59:19.274300Z

it still kinda sucks because you really don’t have any way of pausing or halting a slow update

lilactown 2020-04-30T20:00:05.275200Z

there’s no natural point to check to see if there’s higher priority work to be done

dominicm 2020-04-30T20:00:09.275400Z

Doesn't sound like react does that in the gist you linked, but I'm guessing that'll change in concurrent mode.

dominicm 2020-04-30T20:00:37.275700Z

But that's exactly how facebook do async with php, they make a state machine and after each "state", they check if there's other work to do.

lilactown 2020-04-30T20:02:03.276Z

> If the deadlines are not exceeded, the renderer will render and commit them one-by-one, stopping whenever it is going to exceed its frame time budget which it receives from `scheduler`. This can happen even in the middle of processing a single batch. There is a cooperative yield point after processing each individual fiber.

dominicm 2020-04-30T20:02:33.276100Z

yeah, each fiber. But they're restricted by the size of a fiber.

dominicm 2020-04-30T20:02:39.276200Z

To get smaller you have to split it up

lilactown 2020-04-30T20:02:45.276500Z

yeah but that’s way better than a whole tree!

lilactown 2020-04-30T20:03:41.277500Z

that was my point… there’s no obvious point in the middle of a Redux reducer or re-frame event to do that to me yet

lilactown 2020-04-30T20:04:18.278300Z

I guess what you’re saying is that is where the state machine comes in, basically you end up wrapping each computation step within the reducer in an await

lilactown 2020-04-30T20:04:40.278600Z

that sucks

dominicm 2020-04-30T20:05:00.278700Z

yeah, that's all that core.async/go, await, clr, etc. do under the hood.