Keeping Client State Up-To-Date In Reagent / Clojurescript

2.9k views Asked by At

I am not sure on the best way to go about this:

I have a web application I am writing which implements basic CRUD functionality for a number of "Project" objects. So, a user can create his/her own set of Projects.

I have written a REST API for adding / removing / listing the projects, and I have written a Reagent front-end client. The client's projects are held in a reagent atom (ratom), as you would expect.

This is where it gets interesting.

In all the tutorials I have seen, it shows you how to update the ratom and your GUI will update automatically. I have that, that works.

What I am currently doing is, at the point of updating the client state, I make my REST POST call to update the server state on the database.

But, this doesn't feel right. I am now storing state in two places: on the client and on the server, and both could be different.

If I were implementing this as a plain old web page, the server would send a page back to the client with the latest state of the database in it. But, state already exists on the client in the ratom, and I don't know how I should synchronise it.

Should I make another call to the server (a GET request) after the initial POST is successful? This could replace the contents of the ratom. Then I'm making two requests.. which seems wasteful.

Or is the best practice to use something like chord or sente to manage the client state asynchronously. This would be better, as it would mean changes from other users would be instantaneously refreshed on the client side. But, I can't see any tutorials demonstrating react with either of these so maybe I am on the wrong track.

So the question is simply, how should one combine react with REST?

thanks

3

There are 3 answers

3
schaueho On BEST ANSWER

I agree with @myguidingstar that your problem is more about client-server synchronization than about clojure or reagent. You could have a similar problem, say, with GWT (been there, ...)

Should I make another call to the server (a GET request) after the initial POST is successful? This could replace the contents of the ratom. Then I'm making two requests.. which seems wasteful.

You seem to miss that POST requests can also trigger responses from the server.

The big question here is why do you need the DB state on the client? Do you have requirements which force you to minimize the amount of GET requests to fetch data from the server? Or do you have business logic implemented on the client (ClojureScript/reagent) side that just doesn't need server interaction? You need to consider also the other side of the same issue: Is it okay if your client state doesn't get updated in a while or could you run into consistency issues because "something else" is modifying the data on the server behind your client's back?

Finally, you say that you

.. don't know how I should synchronise it.

What exactly are your issues? Why not just update the app-state (swap! app-state update-in ...) after you've fetched the data from the server, as described in the reagent-tutorial?

0
Tim X On

As others have noted, this is a common design challenge for any client/server application where there may be more than a single client. I think the key is in looking closely at what state you need to maintain in your client. Questions like "Does your client actually need up-to-date state information regarding the server or remote database or does it just need up-to-date information regarding the actions performed by the client? For example, do you need to know what is in a remote database table after an update or do you just need to know whether your update was successful or not?

The other thing I've seen which can lead devs down the wrong path is owrrying about efficiency too early. To gain efficiency, the design sometimes attempts to incorporate too much state in the local client, believing this will reduce the number of calls to the remote server and make the application more responsive. However, often you can end up with an overly complex implementation which is hard to get right with lots of local state management overhead, often including a lot of information which is only used/relevant in limited or infrequently used parts of the client interface. Next thing you know, your app is losing efficiency as it works to maintain state synchronisation which isn't needed.

The challenge here is due to our tendency to think about problems in a serialized manner. This is natural. However, the problem here doesn't quite fit with this way of thinking. The remote state of the system is changing at an unpredictable rate. Even if you adopt an architecture which pushes changes out to the client, you will run into problems related to working out how often such changes should be pushed out and then how to reflect those changes in the interface such that the user stil gets a consistent and meaningful picture.

My approach, which is just one of many and may not be the most ideal, is to model the web app along the same lines as many database clients (and to some extent even Clojure's own STM). The state in the client is a snapshot of the remote state at a particular point in time. It may not be accurate or up-to-date, but it should be consistent. When the client submits a change, it may or may not succeed, depending on what has changed in the server since I started to enter my change in the client. If it succeeds, it succeeds totally and if it fails, it fails totally.

As pointed out by others, this has no direct relationship to Reagent. This is a borader design issue. However, it may have an indirect impact. For example, when you update the reagent atom, your reagent contain er will be rendered. The atom is aware when it is updated, but it is not aware when the update is only a refresh of existing information with no change, so you really only want to update the atom when it needs updating because something has changed. However, unless this is happening really really frequently and your component is really really complicated, you probably won't see any real impact even if you do frequent refresh type updates anyway.

I would focus on keeping state to a minimum and developing a really simple design, then load test and work out if there are performance issues and deal with them as they arise.

2
myguidingstar On

This is a big question about client-server synchronization that most web apps struggle with.

There's a library by Tonsky for the very problem:

https://github.com/tonsky/datascript

Read his blog post and the example datascript-chat to understand the design. This will require a lot to learn, but I'm afraid it's the only way that is not kind of "doesn't feel right".