When developing simple client-side React applications, one often needs a touch of backend services. Not an entire database (with its management & administration needs) or high performance compute. These services have their own use cases, but for simpler requirements, they often take more energy to set up than necessary. It’s also difficult to scale these services up or down depending on usage, which might lead to unnecessary overhead (when you don’t utilise them fully) or service interruptions (due to traffic spikes).
Enters the notion of serverless infrastructure – a paradigm of providing backend services to the front-end on a as-used basis. Serverless vendors charge usage, not bandwidth and hence solve two problems with one solution – that of boosting (cost) efficiency and scaling up on demand. This creates a refuge where application developers can write and deploy code without worrying about infrastructure needs. Like all refuges however, there are trade-offs in inhabiting this system. For the usecase detailed in this post, serverless computing suffices and I’ll outline how I used Cloudflare’s edge network as a serverless platform.
Cloudflare workers are JS modules / functions running on Cloudflare’s global network in over 200 cities around the world. Cloudflare’s reputation of providing DDOS protection services implies that they have spent time optimizing resilient cloud networks and CDN services, and they offer to front web traffic for other businesses. So in essence, a cloudflare worker is a reliable online JS function which is run on CF’s network servers anytime you call the function. This is great for non-persistent (light) compute, but the workers platform also includes simplified key-value storage and consistent distributed storage via Durable Objects. Of interest to us is this last one, namely Durable Objects. We want to implement a chat application (with it’s UI layer written in React) over a global distributed network.
Suppose there are five participants in different parts of the world, talking in a group chat. To provide real-time feedback, one needs low-latency coordination among the servers closest to these users and even more importantly, the messages in the group chat have to be in one order for all users (a fairly difficult problem). See how quickly the problem of enforcing consistency over distributed systems becomes demanding? Cloudflare’s Durable Objects try to tackle this by implementing a locking mechanism to enforce Global Uniqueness and using a transactional single-threaded storage API while reading / modifying key-value pairs. This suffices for a wide variety of use-cases (certainly ours in this blog post), but you can also think of use-cases where you might need more local (& better) consistency resolution (like in one particular geographical region).
fetch listener (wrapped in an error handler) and passes on API
requests further, to instantiate a ChatRoom class. The instance of this class
runs as a Durable Object and creates chatrooms while handling sessions to this
chatroom. Since we don’t want to persist data, we don’t store it anywhere and
all data is lost as soon as the websockets enabling the sessions are
disconnected / closed. The ChatRoom Durable Object also instantiates a
RateLimiter class to ensure worker compute is not abused by heavy traffic, but
is rather moderated via rate limiting.
One of the issues I came across while implementing the UI in React was to
re-render the components upon receiving messages through the web socket. React
components usually do not re-render until some state (which is being accessed in
the component) is updated. This is, in general, a strength of React as it promotes performant rendering while defering expensive operations – namely, re-render only when necessary. So when sometimes there is no explicit need of state in a component, external triggers of rendering are carried out by dummy state, like in this instance, a dummy
msgCount state number.
Another issue I faced was sometimes encountering incoherent state updates (and
renders) in my App component. When using the
useState hook, the setter
function (some form of
setState) is used to update local state and ask React
to re-render the component (update the DOM). Usually, one often just calls
setState with a new value passed in as an object, for e.g.,
setState(newState). What if our
newState depends on the current
variable? Directly accessing
state is not a reliable way to cause this
update as React handles state updates asynchronously. This doesn’t show easily
in all applications (for instance, when some other state variable being updated
pushes this state variable to also be updated and saves our application from
breaking) or when the state update is a rather isolated low-cost operation
(which somehow escaped React’s batching of operations for the next render). To
this.state may be updated asynchronously, you should
not rely on their values for calculating the next state.” Now I usually don’t
face these failed “renders”, but in this app, frequent websocket messages (which
often happen in fast succession) “failed to re-render the UI” and any further UI
updates almost “missed out” the state updates before (for e.g., if you’re
messages list with messages flowing from a websocket). At first, I
tried to fix this by adding more dummy state variables (without success) and
useRef hooks to force more re-renders, before finally
realizing the main problem. What I wanted was not more renders, but more
reliable state updates, as the excess renders also kept showing the old state,
although sometimes incoherently. It wasn’t the renders that were failing, but the state updates.
The conclusion is to pass callback functions to
setState hooks when one needs
the current state or props to calculate the next state. React is then able to
reliably queue up these “pipes” / transformations to apply to the current state,
without relying on what the current state actually is. This is almost so
important that there should be an ESLint plugin for this (and there is
This app has been pretty useful to me to acknowledge the subtleties of React’s
useState hook (and rendering mechanism) and to understand how capable
serverless infrastructure is. Try out the
app, and let me know your thoughts!