Paramjit Singh

Oct 6, 2022

React Berlin meetup Oct '22

I spoke at the React Berlin meetup today (slides below), about my experience writing a chess transcriber in React.

Coding a fairly complex UI in React led to some realizations around component architecture & state management. Components should be divided by functionality and often central stores (like Redux) are unnecessary even for deep UI trees.

More about this can be read at my other blog post.

Oct 4, 2022

Implementing serverless chat in React

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.

When I first started this project, I wanted to build a live chat application, one of the fundamental pieces of software in any social product. I made an app using the popular Javascript express (server) and socket.io (websocket) libraries which worked fine in local development. When it came to deploying it online, I didn’t want to just deploy it openly (without authentication) as there might be bots crawling the internet, looking for open websocket servers to spam and so on. On the other hand, I didn’t want to use my domain for authentication (and registration) services for this pet project. The idea of using serverless JS workers came to me as I was thinking about how to architect the application with the least possible backend exposure. Luckily, several cloud vendors provide such services nowadays so it’s only a matter of time to integrate with their interfaces.

My live chat React app

Cloudflare workers

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).

Our worker implements a 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.

Passing callback functions to React’s useState

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 state 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 quote React’s documentation, “Because this.props and 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 appending a messages list with messages flowing from a websocket). At first, I tried to fix this by adding more dummy state variables (without success) and using useEffect 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 one).

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!

This article was cross-posted to Medium & Dev.to, so you can also follow there or post comments.
Sep 17, 2022

Implementing a chess transcriber in React

React is today one of the most popular frameworks for coding UI layers of modern web apps. By the moniker “modern”, I mean an era where best practices have matured for the vast majority of use cases & some level of declarative programming has been achieved (you code what should happen, instead of how to do it).

I think there are two fundamental aspects of React’s popularity (especially compared to the frameworks / libraries used before it):

  • Component architecture. Stemming from the CS notion of separation of concerns, React simplifies building complex apps by encouraging modularity (plug-&-play), reusability (DRY) and clear abstraction. This aspect has been kept in mind in the former frameworks as well, and React continues to implement it (much to the benefit of engineers for whom React is their first framework). Note the emphasis of React’s separating concerns despite coupling HTML/CSS and JS.

  • Reactive programming. This is a declarative paradigm to create systems where asynchronous functions automatically handle updates to static content, without the programmer having to explicitly implement the updates. Over the last decade, this has transformed the engineering world with the rise of the internet (aka asynchronous land). Event loops/queues & efficient updating of declarative code has been extensively used to design reactive systems and React is a library bringing similar patterns to web UIs. Like in much of engineering, while imagining a system where only the updated UI pieces (only things to be done) are re-rendered (get done) is easy, implementing it in reality is hard (browser DOM operations are expensive & not designed for it). React tackles this by using a reconciliation algorithm on a virtual DOM (a JS object representing the DOM tree) to handle this reactively.

Chess transcriber

Here I want to talk about how I implemented a chess transcriber using React (source).

  • Designing component architecture. The UI has a decent level of complex logic to it. At first, I divided the app in a Left Sidebar, main Chessboard and the Right Sidebar. The Left Sidebar should state instructions on how to use the app & let you choose functionality, the Right Sidebar should give functionality-specific instructions. And the main Chessboard is where all action should happen.

  • Implementing the Chessboard took some thought and really capitalizes on the abstraction model of React. The entire Board (enclosed in a wooden box) has 64 Tiles, where each Tile’s props characterize it’s color, an onClick event listener, the piece on it & options to highlight it. I used the chess.js library to handle the underlying chess logic (the library has been coded over 10 years and provides an excellent API), and designed the UI dataflow keeping the library’s interface in mind.

  • Handling the application state. At first, I tried passing props around to handle the UI updates, but it quickly became too complex to deal with. Not that it couldn’t be done, but the resulting design was ugly (not well abstracted, lots of re-implementation of code & lots of passing data to places where it doesn’t exactly need to be). I researched a bit, and indeed there are central state store implementations (often with the Flux architecture), which people like to use for complex component trees. I chose Redux to store where the pieces exist on the board & which are the highlighted tiles for a move, etc., and made the app work.

My chess transcriber React app

A second look & re-architecture

The more I learnt to think in React (and since I wanted to improve the app), I thought through exactly which component needed which data flow and how to arrange them in a well-organized dataflow-lean way. I decided to re-architect the app making the following changes.

  • Component architecture. At first, I had divided the components focusing on UI, but that seems like a mistake. I have now divided the components based on functionality. The UI is an implementation / representation of the underlying UI logic — the main app (in JS) which says what is happening. This app (like any application code solving business logic) is divided into functions (i.e., by functionality). It often happens that this translates to separate UI components, but it’s important to note that the underlying driver of this abstraction is functionality, not UI. (For example, you can have a UI mixing up all outputs of various components in a well-written React app).

  • State management. I realized that my app (much like a lot of apps out there) doesn’t really need to use Redux or even React’s Context API. This came in time when I realized that using Redux really makes sense when you want the actions (state changes) themselves (whether for bug reports, persistent storage or syncing UI across network). That is, in a way, Redux helps to decouple the actions from the components themselves. Even deep component trees which on first glance seem like they would benefit from a central store (via Flux or Context) can often be rearranged in a way that encourage component composition with appropriate local state initialization / usage.

This app has been pretty useful to appreciate the underlying design choices of React and to understand when exactly to use (or not) Store libraries. Clean code comes from clean architecture, which requires having a clear idea of what exactly various tools offer, and when they make sense to use. Try out the app, and let me know your thoughts!

This article was cross-posted to Medium & Dev.to, so you can also follow there or post comments.
Sep 5, 2022

Your users are not your sole audience

One of the first things beginners in software engineering learn is to dote on their source code. As if getting up to speed with the latest tech stacks & platforms was not challenging enough, the industry (not without reason) places great emphasis on how you code. One hears about phrases like human centric design & testability, and wonders what the problem is if one’s software just works.

Why can’t the thing just work?

One needs to undergo a mindset shift to realize that code must be written not only to make the end product work, but also to be readable to other developers. That is, of course, if one does not wish to solo their way through the entire project lifecycle (not a great idea for anything long term).

So the idea to internalize is that your source code is also a product. That’s the product you use to demonstrate your skills (sell) to your future employers/collaborators, with whom you then collectively sell your software to users. Once you have this mental model set up, it’s much easier to make sense of software engineering culture and **understand where the vast amount of literature (blog posts, video, podcasts, etc) comes from.

Social trumps all

Human beings tend to get social about the things they do, no matter how technical or mathematical they are. That’s called Conway’s law or something. Namely that organizations design systems that mirror their own communication structure. What happens if you apply this law to an industry? See what I mean?

People generally also care more about the social aspect of their work than just technical correctness, or even mastery. That’s why opinions, conventions and standards start to matter. Developers have tastes and it’s easier to work together when those tastes are in tune. That’s one reason (among others) why you hear empathy talked about so much in developer circles.

Organizational thinking

There’s another important aspect. Any large influential project or organization must necessarily involve many people. While it may naively seem that multiplying effort multiplies output, it takes great foresight & management skills to make one plus one greater than two.

Behaving oneself

Most of the non-programming skills engineers need to master are just that. Project management skills in the context of software. While much of this is commonly learnt like gospel, in a “this is how you do it” sort of way, there’s value in sticking to the basics and realizing that professional engineering is a social process, and there are notions of manners. While many of these manners aim to improve productivity / simplify architecture, some of them have idiosyncratic origins. And that’s ok. Just important to distinguish them from the technical skills, to understand the motivations behind workflow choices.

This article was cross-posted to Medium & Dev.to, so you can also follow there or post comments.