Frontend

React == PHP & Angular.revolution() – Frontend Monthly May 2023

There was a lot going on in May 2023. Next.js 13.4 clearly showed the direction that React is currently heading, Angular underwent a total revolution, and Vue lived up to its long-awaited version 3.4.

Article cover

Before we get to today’s report, we owe you two or three words of explanation. In April, Vived went through a little turbulence (nothing terrible – the editorial team is still working full out to deliver fresh and interesting content for you), and all this was compounded by my almost month-long vacation (if you’re wondering whether USA west coast is worth visiting – it’s totally worth it!). As a result, frontend summaries disappeared from the radar for almost 2 months…. Now, we are back with a slightly revised formula. Instead of short weekly summaries, you can expect a bit longer monthly reports and occasional special editions. I hope you will like the new format as much as the previous one, and without further ado – grab a cup of your favorite beverage, sit back and enjoy the read!

Remember, this is the only right position to read Frontend Monthly looks like!

React 19 Next.js 13.4

If you’re interested in React news, the best thing to do is start following what’s happening in the Next.js ecosystem. Currently, the React team is focusing its attention on the development of React Server Components(RSC). Due to the fact that this functionality cannot be used without the framework, Meta has started working closely with Vercel (Next.js’ parent company) and it is Next.js that is currently pioneering the implementation of new React features. Next.js 13.4 released in early may marks a major milestone in this regard, as the new router based on React Server Components becomes an officially recommended solution. Before we move on, let’s take two steps back and remind ourselves what React Server Components are and how the new page router works in Next.js.

React Server Components

React Server Components were first presented by Dan Abramov in December 2020. In its principles, the concept is simple – some of the components will be rendered on the client side and some on the server side. With a clear separation between server and browser, server-side components will get access to new APIs and will be able to directly query the database or use other Node specific interfaces. Developers will also be able to stop worrying about the size of dependencies in server-components, as these will never be sent to the client.

"use server"

import db from 'db'; 
//This is interactive client component
import NoteEditor from 'NoteEditor'; 

async function Note(props) {
  const {id, isEditing} = props;
  const note = await db.posts.get(id);
  
  return (
    <div>
      <h2>{note.title}</h2>
      <section>{note.body}</section>
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}

The React Server Components architecture differs from the standard server-side rendering approach. First, in the standard approach, the server waits until the entire page is rendered. This means that if the server needs to perform a slow query, the entire page will load slowly. Using React Server Components, the skeleton of the application quickly arrives in the browser, and the subsequent components are gradually streamed as pseudo HTML. Secondly, in the standard approach, the client-side framework takes control of the application, resulting in a requirement that all components needs to work both on the client and on server side. With React Server Components, actuall code is never sent to the client and rendering is always done on the server.

React Server Components also have some limitations. First, due to a completely different life cycle, the useState and useEffect hooks cannot be used. Second, we can’t switch between server-side and client-side rendered components freely. Being more precise, client-side components cannot directly import server-side components – it is only possible to pass such a component as one of the props. Although on the surface this may seem like quite a limitation, in the end it does not cause many problems.

Since 2020, the React team has been working hard to develop React Server Components. After 3 years of intensive development, we finally lived to see a reasonably stable API. If you are hungry for more details about server components, I personally recommend Dan Abramov’s original presentation and a great blog post from Plasmic Blog covering this topic in detail.

Next.js Page Router

With Next.js 13 published last autumn, a new routing model for Next.js was unveiled. It introduced a lot of of small but important improvements. Among them we can find native support for nested layouts, better error handling and new options for managing loading states. Among the new features, the most important is the fact that the new router is built on top of React Server Components. This means that all components are server components by default, and the future of Next.js is closely related to the future of React Server Components. If you are curious about the details of how the new Page Router works, we have devoted one edition of our report to this topic.

Next.js 13.4, Page Router i React Canary

If you’ve connected the dots properly, you’re probably scratching your head right now – How is it that Next.js is recommending the use of React Server Components, if they are not yet stable functionality? This is thanks to the new release channel the React team has decided on. Functionality with a stable API will be coming to the Canary version. Why didn’t the team just decide to release the library more often? Sometimes a release gets locked on a few missing functionalities completely unrelated to the stabilized API. With a new release channel, the community will be able to test those new functionalities faster, while not worrying that the next experimental version will completely break their applications.

React Server Actions

If you regularly read our reports, you’ve surely seen more than one meme about how React is becoming similar to PHP. Well, with Next.js 13.4 another step in that direction has been taken. React Server Actions allow you to call functions on the server and completely hide the communication layer from the developers. In other words, this is just good old Remote Protocol Call1. All we have to do is start our function with use server; and voilà – the code will be executed on the server.

import { cookies } from 'next/headers';
 
export default function AddToCart({ productId }) {
  async function addItem(data) {
    'use server';
    const cartId = cookies().get('cartId')?.value;
    await saveToDb({ cartId, data });
  }
 
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

Importantly, React Server Actions have their limitations. First, a function of this type must be asynchronous. Second, we can pass such a function only to the form, or to the startTransition hook. Interestingly, in the first option, the form will work even if the client disables JavaScript on its side.

React Server Actions are definitely not the first approach to RPC in JavaScript, but they are the first implementation integrated into a popular frontend framework. If they manage to leave the alpha state, they will probably become an interesting extension to React Server Components.

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

Angular 16

Next versions of Angular are released punctually every six months like the Japanese Shinkansen and in may Angular 16 has been released. Looking at the last few versions of the framework, one can’t help but notice that its development has greatly accelerated. Importantly, the team behind the framework has finally started listening to the community’s concerns. From version to version, developers’ biggest pain points are now being solved – even those that have accompanied the framework since the dawn of time. Angular 16 continues this trend and offers a revolution that Angular hasn’t seen before.

Signals

Angular was built on the foundation of a relatively small library called zone.js. It is responsible for monkey-patching asynchronous browser methods. Whenever such method is called, zone.js notifies Angular, and it tries to determine whether the application’s state has changed. Thanks to this architecture, Angular is able to refresh the variables in the user interface in a relatively “magical” way.

Based on famous: https://xkcd.com/2347/

As is usually the case, magical solutions also have their dark side. To determine what has changed in the user interface, Angular has to perform a heavy and complicated Change Detection operation. If you’ve ever had to optimize Angular components or encountered interface refresh errors, you definitely know what I’m getting at. The magic of Angular can lead to performance and debugging problems pretty quickly.

Over the years, Angular developers have learned to manoeuvre between the limitations of zone.js and Change Detection, but they still felt it could be done better. This better way is called Signals and it is the first step toward getting rid of zone.js. It probably won’t happen soon (after all, there are quite a libraries heavily based on zone.js behavior…). However, it seems that sub-trees of zone-less components are just around the corner.

But let’s get back to the clue of today’s topic and look at what signals actually are. To quote from Angular’s documentation: a signal is a wrapper around a value that is able to notify interested consumers when that value changes. For anyone operating in Angular and RxJS , the analogy to BehaviourSubject may be clearer, since that’s exactly what signals are.

Signals are created using the factory method taking an initial value as a parameter (signal('Jane')) . Signals can be composed using the computed() method. It is also possible to call side effects when the value of a signal changes by registering the appropriate callback in the effect() method. At any time we can read the value of a signal by calling it like a function ({{ fullName() }}). Signals can be updated using the set(), update() and mutate() methods. In a nutshell, that’s all you need to know about signals. If you are curious about the details, we have written entire frontend report on this topic.

@Component({
  selector: 'my-app',
  standalone: true,
  template: `
    {{ fullName() }} <button (click)="setName('John')">Click</button>
  `,
})
export class App {
  firstName = signal('Jane');
  lastName = signal('Doe');
  fullName = computed(() => `${this.firstName()} ${this.lastName()}`);

  constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
  }

  setName(newName: string) {
    this.firstName.set(newName);
  }
}

In addition to the new primitive, the Angular team is also working on a number of integrations built into the framework. So we’ll get tools for convenient integration between streams and signals, APIs of the framework that return signals instead of streams and reactive inputs based on signals. The latter in particular looks particularly interesting.

Since Signals behave very similarly to BehaviurSubject, does Angular need another primitive integrated into the framework? First of all, Signal is a much simpler abstraction than Observable. In order to operate RxJS efficiently, we need to learn about a bunch of complex issues such as cold and hot streams, asynchronicity in JavaScript and the more than 100 operators available in RxJS. Second, all streams naturally gravitate toward cold streams (just apply the map operator on any BehaviourSubject and it becomes the cold stream). This makes it so that we can never be sure that a subscription to a stream will synchronously return a value. As a result, our applications are flooded with unnecessary and repetitive code. In summary, RxJS works great when we need to model complex events happening over time, such as limiting queries to the backend or aggregating mouse clicks. When it comes to component and application state management, RxJS has more disadvantages than advantages.

Non-Destructive Hydration

Hydration is the process during which HTML rendered on the server is taken over by a client-side framework. Most frameworks connect listeners to existing DOM nodes during this process. Until version 16, Angular, instead of using the existing DOM, relied entirely on the structure generated on the client side. As you can guess, this occasionally caused minor glitches and was not the most optimal strategy. As of the latest version, after setting the appropriate flag, client-side Angular will be able to operate on the HTML structure rendered on the server.

Vite + ESBuild = Speed

If you have never complained about slow Angular builds then…. you’ve probably never worked with a large project in Angular. For several versions now, Angular has been making big improvements when it comes to development server startup time. Angular 16 completely migrates the development server to Vite + ESBuild. On the one hand, the performance improvements are pleasing, on the other hand, the disconnect between the tools used in production (where webpack still builds the application) and locally is a bit worrying.

Vue 3.3

We’ve been waiting for Vue 3.3 for a long time. We already wrote in our December summary of the year that the next Vue would be released in a few weeks. Along the way, the team led by Evan You encountered quite a few perturbations and from January it suddenly became May. Not surprisingly, one of the team’s goals for the next few months is to deliver smaller, incremental releases on a regular basis. Time will tell whether the goal will be met, but Vue 3.4 is expected to be released not later than in six months.

TypeScript, TypeScript, TypeScript

The main theme of Vue 3.3 is better integration with TypeScript. Let’s start with support for complex types in the defineProps and defineEmits functions. If you have never used Vue, these functions are used to define the input and output pattern of a component. Vue used the type passed to generic methods in the compilation process, which limited the scope of possibilities to simple types defined in the same file as the component. With Vue 3.3, these limitations disappear completely, and defineProps and defineEmits can now take any type – imported from other files, Union Types, or even advanced monsters using Utility Types.

<script setup lang="ts">
import type { Props } from './foo'

// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>

Another new feature in Vue 3.3 are generic components. I’ve been trying to dig up information on how Vue developers have dealt with generic components so far. It turns out that they either abandoned type checking altogether, or were doomed to messed up and complicated solutions. Fortunately, a reasonable solution is already built into the framework.

<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
  selected: T
}>()
</script>

Other improvements

In addition to improved integration with TypeScript, Vue 3.3 is also a package of minor improvements. For example, we got a new, definitely neater API for the defineEmits method.

// BEFORE
const emit = defineEmits<{
  (e: 'foo', id: number): void
  (e: 'bar', name: string, ...rest: any[]): void
}>()

// AFTER
const emit = defineEmits<{
  foo: [id: number]
  bar: [name: string, ...rest: any[]]
}>()

In addition, we got a new method defineSlots, which can be used to define the types of slots in a component. At the moment, the compiler only verifies the compatibility of the component schema, making it impossible to define a finite list of accepted components. The solution is also not supported by Volar, the Language Server used by Vue. Despite the minor limitations, defineSlots seems to be a step in the right direction and is sure to please a lot of people working with Vue on a daily basis.

<script setup lang="ts">
defineSlots<{
  default?: (props: { msg: string }) => any
  item?: (props: { id: number }) => any
}>()
</script>

The future of Vue

Comparing the new features from Vue to those from Angular and React, one can’t help but feel that they are a little less exciting. Vue is facing the problem of migrating users from version 2 to 3. This means that the team cannot focus entirely on the development of new features. After all, the previous release of Vue was not Vue 3.2, but Vue 2.7, which ported new features from Vue 3 to Vue 2. Everything looks like that the situation is about to change. First, Vue 2 will reach the end of its life with the end of this year. Second, at this year’s Vuejs Amsterdam conference, Evan You revealed that the team is working on a new efficient compiler that will be heavily inspired by Solid (and this is definitely exciting feature!). Third, Vue is not just Vue. In recent months, tools built around Vue like Vite or Volar have started to gain popularity outside the parent framework, and that’s where the most exciting stuff is happening right now.

PS If you work with Vue on a daily basis and have not yet seen Evan’s presentation from Vuejs Amsterdam, it’s high time to catch up.

Qwik 1.0

Qwik is a framework developed by a real frontend all-stars team. In the team working on the framework we can find, among others, Miško Hvenry (co-creator of Angular) and Adam Bradley (co-creator of Ionic) – people who know how to build and sell a framework. In addition, behind Qwik there is the widely recognized company called Builder.io. In its portfolio we can find, among others, the Partytown library (which allows you to move the code of 3rd party trackers like Google Analytics to Web Workers) and Mitosis (which allows you to write components in a special meta language and then compile them into any framework).

Qwik turns the current server-side rendering paradigm upside down by getting rid of the hydration process. Hydration, is a client-side process during which a server-side rendered application loads JavaScript code, then repeats all the initialization logic and finally plugs the appropriate listeners into the existing DOM structure. While the client quickly sees the application, the hydration process can take up to several seconds. Until then, the application remains completely un-responsive.

The alternative that Qwik offers is resumable execution. This paradigm saves the state of the application from the server so on the client side, code execution can be resumed immediately. In addition, Qwik divides the application into very small chunks and does not download the entire application code at startup. Instead, advanced pre-loading strategies are used that only load the code of necessary components. Quite advanced tools are also put into the developers hands to enable setting clear priorities of component downloads. If a component is unimportant, it is even possible to delay download of its code until the user interacts with it.

At first glance, Qwik’s syntax is very similar to React’s syntax. Therefore most developers will quickly pick up on it. Ubiquitous $ ending many function definitions may surprise some developers but they designate the chunks into which Qwik will divide the application and are crucial for advanced optimizations.

import { component$, useStore } from '@builder.io/qwik';

export default component$(() => {
  const store = useStore({ count: 0 });

  return (
    <main>
      <p>Count: {store.count}</p>
      <p>
        <button onClick$={() => store.count++}>Click</button>
      </p>
    </main>
  );
});

On the one hand, Qwik very much resembles React, which makes it accessible and understandable for beginners. On the other hand, the mental model of dividing code into thousands of small pieces and downloading them asynchronously can introduce a fair amount of overhead on developers. Personally, I haven’t had a chance to play with this shiny new toy yet, and I’ll reserve final judgments. However, I have a subconscious feeling that architectures such as Dynamic Islands or React Server Components don’t try to solve all the problems at once and offer far simpler mental models.

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

PS: React and PHP’s birthday 🎉

If you haven’t had a chance to celebrate this month, I’m already rushing to relieve you. In May, PHP celebrated its 28th birthday, and its younger colleague in the form of React celebrated 10th. A perfect opportunity to reminisce a bit, eat your favorite cake and drink a glass of your favorite dring. See you next month!

Sources:

https://nextjs.org/blog/next-13-4
https://react.dev/blog/2023/05/03/react-canaries
https://blog.angular.io/angular-v16-is-here-4d7a28ec680d
https://blog.vuejs.org/posts/vue-3-3
https://www.builder.io/blog/qwik-v1