Enterprise Javascript
5 Modern Trends in React Development
Mo Sayed, Principal UI Developer
7 February 2023
Introduction
As reported in the recent survey: State of JS 2022, React is the most popular UI library/framework for developing User Interfaces (UI). As a tool, it continues to evolve with recent developments such as:
- React Server Components
- Suspense
- ConcurrentMode
- Hooks
- React Context
Discussion of these is outside the scope of the current article. Rather, I'd like to explore developments in the wider React ecosystem such as:
- Techniques for separating client-side and server-side state
- The growing use of server-side rendering frameworks
- The increasing popularity of Prisma
- New ways to handle loading status for API calls
- Increased use of Design Systems.
Each of these is discussed in more detail below.
Discussion
1. Techniques for separating client-side and server-side state
Application State Management is one of the hardest problems to solve within the UI domain, explaining why there are > 50 libraries dedicated to it, including Redux, MobX, and Zustand. Typically UI state can be divided into two main types:
- Client State - this controls interactive parts of our application e.g. showing/hiding a modal, buttons, forms, etc.
- Server State - The response from a Server such as a list of Posts.
It has been common practice to treat these the same and bundle them together into a single data store. However, in recent years, luminaries such as KC Dodds & Tanner Linsley have argued that this may not be the best approach
Consider the difference between the two:
These differences offer a host of challenges for the Server State such as:
- Caching
- Deduping requests
- Updating stale data in the background
- Handling mutations (e.g. POST, PUT).
Based on these differences, it makes sense to separate the 2 kinds of states and treat them differently. A practical approach would be to handle Server-State using libraries for caching and managing Server-State like React Query & SWR.
For Client State, we can do the following:
- For small to medium apps, employ hooks such as useState, useContext, and useReducer
- For medium to large apps, utilise a library like Redux, Mobx, etc.
When using useContext for State Management, it is important to be cautious as it can lead to multiple rendering issues. When a context is high in the component tree, a change to any state held in context could result in excessive re-renders and poor performance. To address this, the React team has proposed the development of a new hook, useSelectedContext, which allows for selecting a 'slice' of the state and only re-rendering when that specific slice changes.
2. The growing use of server-side rendering frameworks
Vanilla React applications suffer from the following problems:
- They utilise Client Side Rendering (CSR), which involves sending JavaScript bundles across the network to the client, where the page is built in the browser. This can impact the performance of the application
- As a consequence of CSR, very little HTML content gets sent across the network initially, which can severely impact the site's Search Engine Optimization (SEO).
Addressing these deficiencies has led to the emergence of several React-based Server Side Rendering frameworks (SSR), such as Gatsby, Next.js, & more recently Remix. Rendering the page on the Server means that there's less JavaScript being sent across the network which will boost performance. Further having a page delivered to the browser with a lot more content improves SEO. SSR feels like a return to the old day when web pages were generated on the server and has obvious implications such as:
- Co-location of Client & Server logic
- Increased demand for developers to be skilled in front and back-end development.
- In teams divided into front/back end developers, a potential increase in merge conflict as multiple developers work on the same file. Having developers skilled in both Client & Server logic introduces a barrier to entry for some organisations. However, the availability of tools such as Prisma (see next section) helps reduce the learning curve.
One potential issue with SSR is that it is only as fast as the slowest query. This is because you have to fetch all the content for a page (which could involve several individual queries) before the page can be built on the server and returned to the browser. Fortunately, recent developments in React 18 offer a solution to this in the form of React Server Components (RSC).
This feature allows you to return a partially completed page to the browser where placeholders like spinning loaders (using Suspense) occupy the space where a component is still awaiting some data to be returned. Once the data has arrived, RSC will replace the placeholder with the relevant component which is suitably populated with the new data.
3. The increasing popularity of Prisma
Object Relation Mapping (ORM) tools are libraries that simplify access to databases. They allow developers to query and manipulate data from a database using an object-oriented paradigm, without having to write a SQL/NoSQL query. One such example is Prisma.
This means that instead of writing raw SQL like this:
SELECT * FROM users WHERE email = 'hsimpson@springfield.com';
We can do the following:
await prisma.user.find({where: { email: 'hsimpson@springfield.com' }})
While this is quite a simplistic example, it suggests the benefits of significantly more complex examples involving complicated object graphs with associations across multiple tables.
Prisma offers a host of other benefits such as:
- Type-Safe Access: Prisma uses TypeScript providing type safety when querying/mutating databases. This means that you can catch errors at compile time instead of runtime, reducing the risk of bugs and making the development process smoother and more efficient.
- User-Friendly API: Prisma offers a simple and intuitive API that makes it easy to perform common database operations such as inserts, updates, and deletes. It also enables you to create complex relationships between tables and perform advanced queries.
- Improved Performance: Prisma generates efficient SQL queries tailored to your database, reducing the data transfer between your database and application for optimal results.
- Cross-Platform Support: It supports a number of popular databases, including PostgreSQL, MySQL, SQLite, and Microsoft SQL Server and MongoDB, making it easy to switch between different databases as your needs change.
- Auto-Generated Documentation: Prisma generates detailed documentation for your database schema, making it easy for other developers to understand how your database is structured and how it can be used.
- Efficient Schema Management: Prisma makes it easy to manage your database schema, providing a clear and concise way to define your database tables, columns, and relationships. You can also use Prisma to update your schema as your application evolves, making it easier to keep your database up-to-date.
With its many benefits, a growing user base, and a dedicated development team constantly working to enhance the tool, Prisma remains a highly sought-after solution for developers utilizing modern frameworks such as Next.js, Remix, and others.
It may appear unusual to consider this a significant advancement in the realm of UI. However, Prisma's ability to simplify database access, coupled with the consolidation of server and UI logic in frameworks like Next.js and Remix, makes it easier for front-end developers to delve into full-stack development. Thanks to tools like Prisma, front-end developers can avoid constructing complicated SQL/NoSQL queries and instead utilize a user-friendly interface to achieve the same functionalities.
4. New ways to handle loading status for API calls
It has been a common practice for some time to use loading spinners while waiting for data to be loaded in a component or page. However, this is not always seen as a positive user experience, as spinners do not provide any information on progress. As a result, users don't know how much longer they have to wait, which can create the perception that the wait is longer than it actually is. This can lead to disappointment, such as waiting for a page to load and not having the needed information. This frustration is particularly evident when multiple spinners are displayed on a page like a dashboard, which can make the user wait a significant amount of time before the entire page is available.
An alternative is to use skeleton loaders, which have been found in one study to be perceived as faster than loading spinners. This is an approach adopted by big organisations such as YouTube, Medium & Facebook. However, like loading spinners, skeleton loaders also do not provide any indication of progress and can lead to disappointment.
Avoiding the shortcomings of loading spinners and skeleton loaders is possible by using approaches such as:
- Stale-While-Revalidate (SWR)
- Optimistic Updates.
These alternatives aim to reduce the waiting time and provide more information to the user.
Stale-While-Revalidate is a strategy to show stale data from a cache whilst triggering a re-fetch for new data (revalidate), eventually replacing the cached value and updating the results rendered on the page. An example of this would be showing a list of posts on a page. If the user navigates away from the page and then returns, we show the same data as before from the cache, whilst triggering a request to fetch potentially new data. Upon receiving this data, the cache is updated and the new list of Posts is rendered to the screen. The effectiveness of this approach requires a good balance between Time To First Byte (we show stale data for a shorter period if the request is quick) and how often the data changes on the Server.
Optimistic Update is an approach that treats mutation requests such as PUT/POST as immediately successful and updates the local state before receiving a response from the server. This creates a fast and responsive user experience. If the mutation fails on the server, the local state can be rolled back and a message displayed to the user. Implementing Optimistic Updates requires careful design to ensure consistency between the local state and the server.
Incidentally, React Query and SWR libraries provide both capabilities, resulting in a UI that is both fast and reactive.
5. Increased use of Design Systems.
A Design System consists of a library of reusable UI components which conform to a set of design patterns and standards enabling developers to build consistent and accessible applications quickly. In addition to serving as a common language between different team members such as engineers, designers and product owners, Design Systems can offer the following benefits:
- Scalability - adding more features and products increases complexity exponentially. A design system lets you build more products without incurring this complexity. This works by centralizing and alignment of design and UI decisions
- Maintainability - centralising our design, patterns and standards means that changes in the design system are instantly propagated to all products
- Productivity - Having a library of reusable components reduces the effort required to build applications resulting in accelerated delivery. Developers now mainly have to concentrate on page composition. This will result in increased productivity and reduced development costs.
- Consistency - Enterprise applications have a critical requirement to ensure that User experience is aligned across all products. By exploiting the elements of a Design System, all products within an Organisation can exhibit the same look and feel.
Design systems are increasingly being adopted by small startups to large companies, all of whom seek to profit from the benefits on offer. Companies can incorporate a Design System in one of the following ways:
- Adopting / Adapting an existing Design System (Open Source)
- Creating a Custom Design System
Many large companies such as Airbnb, Uber, and IBM have developed their own Design Systems to help develop their family of digital products. Whilst this can be costly, it enables greater flexibility that may not be possible from Open Source systems.
At Griffiths-Waite, we have implemented our own Design System to deliver a suite of applications for a Key customer. It's based upon the Atomic Design methodology, which is focused on decomposing an application down to its constituent building blocks ranging from simple buttons to advanced tables. Consequently, we could deliver a number of different products using the same UI library and standards.
One of these was a pure React application and the other was a hybrid Oracle JET / React application. The Design System was developed in parallel to implementing the first app and retro-fitted on the second. The consensus on both product teams was favourable toward the Design System with the significant gains being:
- Increased productivity
- Improved consistency
- Reduced development time & cost
The key takeaway was that Design Systems provide an efficient and optimised approach toward application development, especially within an enterprise environment.
Summary
React remains at the forefront of the UI market as it has been for several years. With its ongoing development, the technologies surrounding it have progressed, bringing about advancements such as:
- Enhanced user experience through alternative methods for handling loading states, dividing client and server states, and adopting Design Systems.
- Boosted application performance through the use of SSR frameworks and Prisma.
- Simplified full-stack development with SSR and Prisma makes it more manageable to manipulate data, conduct efficient server-side rendering, and easily construct robust and scalable applications.
- Decreased development time and cost through the utilization of Design Systems.
It will be interesting to review this in a couple of years' time to see what further developments have occurred.
Insights to your inbox
Enter your email below, and we'll notify you when we publish a new blog or Thought Leadership article.