* States that the client needs to keep track of
* States that the server needs to keep track of
Then on top of those there's two more kinds of states that overlap but they're not quite the same thing: * States that only need to exist in memory (i.e. transient)
* States that need to persist between sessions
There's a seemingly infinite number of ways to manage these things and because "there's no correct way to do anything in JavaScript" you either use a framework's chosen way to deal with them or you do it on an ad-hoc basis (aka "chaos" haha).In the last sophisticated SPA I wrote I had it perform a sync whenever the client loaded the page. Every local state or asset had a datetime-based hash associated with it and if it didn't match what was on the server the server would send down an updated version (of whatever that thing was; whether it be simple variables, a huge JSON object, or whole images/audio blobs).
Whenever the client did something that required a change in state on the server it would send an update of that state over a WebSocket (99% of the app was WebSocket stuff). I didn't use any sort of sophisticated framework or pattern: If I was writing the code and thought, "the server needs to keep track of this" I'd have it send a message to the server with the new state and it would be up to the server whether or not that state should be synchronized on page load.
IMHO, that's about as simple a mechanism as you can get for managing this sort of thing. WebSockets are a godsend for managing state.
(Note: Company no longer exists so it is unmaintained, fork at will =)
It's been so long since I've looked at the code but I did write excellent documentation, most of which is generated from docstrings. Example:
https://github.com/liftoff/GateOne/blob/master/gateone/core/...
You can read the documentation here:
https://liftoff.github.io/GateOne/
If you look at the bookmarks plugin you can see an example of how I performed this style of client<->server synchronization using an Update Sequence Number (USN):
https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...
After the web client connects to the websocket all the plugin's `init()` methods are called and the bookmarks plugin's javascript file calls `userLoginSync()`:
https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...
It sends the current USN (which is retrieved from `localStorage`) to the server (the routing is quite sophisticated... Just know that the message ends up going to the correct function =) and if it's different from the USN on the server the server will send an updated list of bookmarks to the client at which point the client will take care of that via its own handler:
https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...
If you examine the bookmarks.js from top to bottom (it's not THAT long) you should get the gist of where and how various states are stored. That plugin mostly deals with stuff in `localStorage` but if you poke around in Gate One you'll see vastly more sophisticated state synchronization and state update routines at work.
In practice, applications need state on both the client and the server. The server needs the authoritative state information (since the client is untrusted), but the client needs to be able to re-render in response to user interaction without a round-trip.
Another thing I like about this is the ability to be able to use Svelte as a templating language rather than Heex.
The difference between traditional technologies that render HTML server-side and LiveView, is a persistent connection to the server which allows it to re-render templates in response to server-side events and patch client-side HTML without writing any Javascript.
As one example of such approaches, .NET has SignalR since 2013.
And WebForms could use designer tooling since 2001, and then there was ASP.NET AJAX Control Toolkit.
LiveView builds on top of Phoenix Sockets. When the page is loaded for the first time, it starts a lightweight server-side process on the BEAM VM. This long-lived process renders the HTML template (which can consist of multiple SPA-style components) and keeps all of the relevant "props" (called assigns) in-memory.
Every time a new event is received, either client-side (e.g. button click) or server-side (typically via a PubSub subscription), that long-running process will update its assigns (props) to reflect the new state. The framework then intelligently re-renders parts of the HTML template which depend on those modified assigns, and sends a minimal HTML diff to the client.
All of this can be done without writing any custom JS. Basic client-side events are usually set up with special HTML attributes like `phx-click=my_custom_event` and automatically wired up by LiveView JS bundle to be received by that long-running process.
I haven't checked but I'm sure there will be a python one too
I don't want to engage more than that, as the horizons comment seems bizarre in the context of the thread
Well, OK. But HN is not the place for that and I refuse to engage further.
Or, when you don't have a runtime -- as is the case with Rust, kinda sorta I mean because technically `tokio` can be classified as a runtime -- then you rely on a stricter compiler.
Both strategies work pretty well.
I am not shitting on C# / .NET, they are solid as hell. But some things the BEAM VM just does better and everyone who worked with old-school VMs (in my case the JVM) and the BEAM VM can tell you that.
But again, you do you, think what you will. ¯\_(ツ)_/¯
When your web application requires the following:
- Large amount of user interaction (requires client side JavaScript)
- Large amount of experimentation (bundle is not static, changes per request)
You are going to want to split up the logic on the server and client to reduce the amount of JavaScript you're sending to the client. Otherwise you have a <N>MB JavaScript bundle of all the permutations you're application could encounter. This may be fine for something like a web version of Photoshop where the user understands that an initial load time is required. But for something like Stripe, Gmail, etc. where the user expects the application to load instantly you want to reduce the initial latency of the page.
You can move everything to the server, but like GitHub experienced you then encounter a problem where user interaction on the page suffers because user actions which are expected to be instant, instead require a round trip to the server (speed of light and server distribution then becomes an issue).
You can lazy load bundles with something like async import, but then you run into the waterfall problem, where the lazy loaded JavaScript then needs to make a request to fetch it's own data, and you're left making two requests for everything.
If you encounter all these problems then you end up reaching for solution which make it easier to manage the complex problem of sharing logic/state between the client and the server.
- State coming from the server as a result of user actions
- Local state which isn't intended to be shared to the server, usually UI stuff.
It appears that's one of their major problems. They're not "building" APIs. They're just slapping "one shots" onto the side of a router whenever the need arises. This speaks to a complete lack of a planning and design phase.
I guess if you want to build something without any plan whatsoever, this might be a way to "improve" that process, but there's a much simpler one that doesn't require your team to become polarized over a framework.
If you run an identical query engine on client and server and then sync data between them, a client can just write a normal query on the client and you get an optimistic response instantly from a local cache and then later a response from the authoritative server. Frankly, this is where most highly interactive apps (like Whatsapp, Linear, Figma, etc) end up anyway with bespoke solutions we're just making it general purpose.
But more stuff like this is always welcome!