Log 0003 - researching reactivity, always automata, the truth about urls, routing down the wrong path, send-on-render

Published on

Researching Reactivity

I've started researching the history of reactive systems. I'm quite familiar with Harel's work on the topic, but I've been branching out into the wider synchronous languages (Esterel, Lustre, Signal). I plan to go deep and follow the chain of references as far back as I need to. From Esterel alone the references go back to Hoare (Communicating Sequential Processes) and Dijkstra (Guarded Commands), and many more.

On the Development of Reactive Systems research network: On the Development of Reactive Systems research network

I'm already finding a lot of material that touches on areas of interest for me, such as extensions of Statecharts and alternative approaches to programming reactive systems. This is particularly useful for my thinking around Eventcharts - I'm realising Eventcharts will need to be something like a synchronous data-flow language.

Network of related papers/authors Network of related papers/authors

Always Automata

From my research into the synchronous languages, I've notice that Automata (Finite State Machines) are widely used for developing reactive systems. Sometimes they're used directly, but in the case of the synchronous languages automata are used as a compile target. There's several reasons they are suitable for this:

  1. Extensive theoretical research - automata are very well understood and have known/verifiable properties, making them suitable for realtime systems that require formal verification
  2. Convenient representation - automata can be represented as graphs in-memory quite easily, making them suitable for real-world software rather than just theoretical toys.

Now, automata are useful tools -- from their simplicity, expressive power, and efficiency --, but they are very difficult to design by hand. Synchronous languages aim at providing high level, modular, constructs, to make the design of such an automaton easier. Synchronous Programming of Reactive Systems* A Tutorial and Commented Bibliography

The translation of ESTERE'L programs to automata bas a major advantage: it permits to perform automatic proofs of properties of the resulting automata Synchronous Programming of Reactive Systems: An Introduction to Esterel

It's interesting to me that there is this reactive primitive in automata that seems 'fundamental' in some sense. Is there a class of primitives similar to this out there?

The Truth about URLs

URL state has been a hot topic on twitter lately. I want to understand what people mean when they say that URL should be the source of truth.

URL state is underrated. Here's an example of how we built with almost zero useState calls. commerce with almost zero useState calls. https://twitter.com/alfarnex/status/1708616754555969779

In addition it also help user to share deep links. Such as sharing a particular variant to his friends https://twitter.com/alfarnex/status/1708616754555969779

This page uses ZERO useState hooks. I built the http://liveblocks.io guides page by fully relying on useRouter to update state, and it's a much better experience than the alternative. URL state is the way. https://twitter.com/ctnicholasdev/status/1708637211321852140

If you find yourself synchronizing some local component state with the URL (for example, query parameters), you'd be better off treating the URL as the source of truth and ditching your local state in favor of getting the value from the URL (or your router) and updating the URL. Kent .c. Dodds 2021 https://twitter.com/kentcdodds/status/1349177826247806976

The problem is the url is just a string. That's a not a very useful representation to manipulate and use throughout the application. I agree that urls should be an input to the application state, but in no way does it replace state management. Query params need to be parsed and validated. It's also helpful to store them in some data structure that will be convenient to retrieve those values in a format suitable to us. There's three real options to incorporate urls into web apps:

  1. The router takes on state management responsibilities (this is the current trend with things like Tanstack Router)
  2. The state manager takes on routing responsibilities (there's been explorations around XState routers, for example)
  3. Router and state manager kept independent with synchronisation between them (something like useEffects to take url params and send them to XState machines)

Routing down the wrong path

Speaking of routers, I had the chance at work to try out Tanstack Router to evaluate it for our application at work. As a preface to what I'm about to say I think Tanners doing some great work fighting the good fight for SPAs. However, my experience looking into Tanstack Router and also re-visiting React Router left me feeling disappointed in the routing landscape. They are clearly heading in a direction that does not align with they way I personally think about building applications.

My main issue is how Routers want to involve themselves in the React tree, managing where Route components are rendered and coupling them with data-loading. To summarise the philosophy of these Routers:

  1. UIs can be broken down into parts (nested routes) that have largely independent data dependencies and interactions
  2. The Router can then handle parallel data fetching, caching, invalidation and piece together the full component tree

I really don't like Slots/Outlets, which are what allows a Route component to declare where nested routes should be rendered as children. I think they make it hard to follow the UI structure and I also don't believe in coupling the component tree to data so strongly. The Routers big sell is they prevent data fetching waterfalls that can happen with components needing to fetch data before they render their children which also need to fetch data. Modern routers knowing the data requirements ahead of time, can avoid this. But this isn't a problem in the first place if you don't fetch data from inside components in the first place.

I've been developing web apps using external state managers for a long time, and they allow for total control over data-fetching independent of the component tree. So Routers are trying to solve problems that I don't have.

The problem is React want to keep all the state under it's own control, which allows it to things like concurrent rendering where it's in control of scheduling. For a long time I've seen a conflict approaching between the ideologies of 'everything managed by React' and 'React is a view library, BYO data'. Routers are simply being caught in the crossfire.

Send-on-render

I see this pattern commonly in codebases that use XState with React:

  1. Multiple machines are instantiated separately (exist in separate actor systems)
  2. React components are used to pass values and indirectly send events between machines

The result is convoluted data-flow which relies on controlling when renders are triggered. I believe it is much simpler and explicit to allow machines to talk directly to each other. Example:

js
// the following code contains a drawer and a modal
// when the drawer is opened the modal should start polling
// when the modal is closed the drawer should also close
const [drawerState, sendDrawer] = useMachine(drawerMachine)
;<CompA
  isOpen={drawerState.matches('open')}
  onClose={() => sendDrawer({ type: 'close' })}
/>

const CompA = ({ isOpen, onClose }) => {
  const [modalState, sendModal] = useMachine(modalMachine)

  useEffect(() => {
    if (isOpen) sendModal('polling.start')
  }, [isOpen, sendModal])

  useEffect(() => {
    sendModal({ type: 'closeCallback.set', closeCallback: onClose })
  }, [onClose, sendModal])
}

Notice how the machines need to pass callbacks around to be able to trigger events to be sent between each other. I won't include the machine code but it's very straightforward for machines to send events to each other in reaction to other events, and much clearer IMO. I need to do some more work fleshing out the examples and suggestions for avoiding these problems. one issue is that managing all the events in a big XState system becomes hard to manage, which is one main things Eventcharts aim to address.