Is React Having An Angular.js Moment?

François Zaninotto
François ZaninottoJune 05, 2023
#react#popular

In 2012, Angular.js changed the landscape of frontend development and quickly became a success. Just two years later, the Angular team launched Angular 2, which was a complete rewrite of the original library based on a different set of paradigms. Many developers, including myself, didn't want to rewrite their existing apps to fit these new ideas. And for new projects, Angular.js wasn't the go to choice anymore, as other frameworks were just as good.

In 2015, we began using React for all of our frontend work. The simple architecture, the focus on components, and the steady productivity regardless of codebase size made it an easy choice. React was a big hit and the community grew very quickly. Recently, the React and Next.js teams have been promoting Server Components, a new way to build web applications that doesn't fit with most existing React app.

Is this change as big as the move from Angular.js to Angular 2? Is React going through a similar phase as Angular.js did?

Note: In this article, I'll discuss new features introduced by both the React and Next.js teams. Since they work closely together, it's hard to say which team is responsible for which feature. So, I'll use "React" to refer to both teams.

Relearning Everything

At its core, React is a view library. This doesn't change: with React Server components, you can still build components with JSX, and render dynamic content passed as props:

function Playlist({ name, tracks }) {
    return (
        <div>
            <h1>{name}</h1>
            <table>
                <thead>
                    <tr>
                        <th>Title</th>
                        <th>Artist</th>
                        <th>Album</th>
                        <th>Duration</th>
                    </tr>
                </thead>
                <tbody>
                    {tracks.map((track, index) => (
                        <tr key={index}>
                            <td>{track.title}</td>
                            <td>{track.artist}</td>
                            <td>{track.album}</td>
                            <td>{track.duration}</td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
}

However, everything else undergoes a change with Server Components. Data fetching no longer relies on useEffect or react-query ; instead, you're expected to utilize fetch within asynchronous components:

async function PlaylistFromId({ id }) {
    const response = await fetch(`/api/playlists/${id}`);
    if (!response.ok) {
        // This will activate the closest `error.js` Error Boundary
        throw new Error('Failed to fetch data');
    }
    const { name, tracks } = response.json();
    return <Playlist name={name} tracks={tracks} />;
}

The fetch function isn't the browser fetch. It's been enhanced by React to provide automatic request deduplication. Why is this necessary? If you need to access the fetched data deeper in the component tree, you can't place it in a React Context because useContext is disabled in Server Components. Therefore, the recommended method to access the same fetched data at various points in the component tree is to re-fetch it wherever required, and rely on React for the deduplication.

This fetch function also caches data by default, irrespective of the response cache headers. The actual fetching process takes place at build time.

If you want a button to initiate a POST action, you now have to include it in a form and use server actions, which means using a function with the use server pragma:

export function AddToFavoritesButton({ id }) {
    async function addToFavorites(data) {
        'use server';

        await fetch(`/api/tracks/${id}/favorites`, { method: 'POST' });
    }
    return (
        <form action={addToFavorites}>
            <button type="submit">Add to Favorites</button>
        </form>
    );
}

The typical React hooks - useState, useContext, useEffect - will result in an error in Server Components. If you require these, you'll have to use the use client escape route, which forces React to render the component on the client-side. Keep in mind, this is now an opt-in feature, whereas it was the default behavior in Next.js before the introduction of Server Components. use client

CSS-in-JS isn't compatible with Server Components. If you're accustomed to directly styling components using the sx or css prop, you'll have to learn CSS Modules, Tailwind, or Sass. To me, this change feels like a step back:

// in app/dashboard/layout.tsx
import styles from './styles.module.css';

export default function DashboardLayout({
    children,
}: {
    children: React.ReactNode,
}) {
    return <section className={styles.dashboard}>{children}</section>;
}
/* in app/dashboard/styles.module.css */
.dashboard {
    padding: 24px;
}

And what about debugging? Good luck. The React DevTools don't display the details of React Server Components. You can't inspect a component in the browser to see the props it used or its children. At the moment, debugging React Server Components means resorting to console.log everywhere.

The mental model of Server Components is entirely different from client-side JS, despite the fact that the base - JSX - remains the same. Being a proficient React developer doesn't necessarily help you much with Server Components. You essentially have to relearn everything, unless, of course, you're familiar with PHP.

Note: To be fair, most of the issues I've raised concern features labeled as "alpha". It's possible that they'll be resolved before the stable release.

Developing With An Empty Ecosystem

As I mentioned earlier, react-query is no longer viable for data fetching. It turns out, it's not the only library that doesn't work with React Server Components. If you've been relying on the following tools, you'll need to start looking for alternatives:

The issue is that these libraries all use standard React hooks, which fail when called from Server Components.

MUI With NextJs 13

If you require these libraries, you have to encapsulate them in a component that forces client-side rendering, by using the use client directive.

To emphasize: React Server Components break almost every existing React third-party library, and these library authors must modify their code to make them compatible. Some will do so, many won't. And even if they do, it will take time.

So if you're starting an app with React Server Components, you cannot depend on the existing React ecosystem.

Even worse: client-side React provides tools for everyday needs that are not yet covered in Server Components. For instance, React Context is a great solution for managing dependency injection. Without React Context, Server Components will likely need a Dependency Injection Container, similar to Angular's approach. If this isn't provided by the core team, you'll have to count on the community to do so.

In the meantime, you'll have to code many things manually. Building a React app without a UI Kit, a form framework, a smart API client, and the React integration of your preferred SaaS can be very challenging.

The current React ecosystem is its most significant advantage. It's what makes React so widely used. And React Server Components disrupt it.

Too Much Magic

Server-side rendering is a solved problem. A server-side script receives a request, fetches the data, and generates the HTML. Client-side rendering is equally mature. The browser retrieves the data, and the client-side script updates the DOM.

React suggests mixing server-side and client-side rendering, using what feels like black magic. You can use client components in server components, and vice versa.

Server Components

When a client component renders a server component, the React server doesn't send HTML but a text representation of the component tree. The client-side script then renders the component tree on the client-side.

If you're used to debugging your AJAX requests with HTML or JSON, you're in for a surprise. Here's a sneak peak at the RSC Wire format that React uses to stream updates from server components to the client:

M1:{"id":"./src/ClientComponent.client.js","chunks":["client1"],"name":""}
S2:"react.suspense"
J0:["$","@1",null,{"children":[["$","span",null,{"children":"Hello from server land"}],["$","$2",null,{"fallback":"Loading tweets...","children":"@3"}]]}]
M4:{"id":"./src/Tweet.client.js","chunks":["client8"],"name":""}
J3:["$","ul",null,{"children":[["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}],["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}]]}]

This format is not documented because it's an implementation detail.

Readability is what has made HTTP, JSON, and JSX so popular. However, React Server Components disrupt this pattern.

React Server Components seem like too much of a mystery because they're difficult for most developers to comprehend or debug. It's uncertain whether this will boost or hinder productivity.

Do We Really Need This?

If you think about web development from first principles, it's logical to conclude that server-side rendering with a dash of AJAX is an effective way to build web apps. Dan Abramov provided a brilliant explanation for the motivation behind React Server Components in his talk at Remix Conf 2023:

React's Master Plan

This architecture is well-suited for e-commerce websites, blogs, and other content-focused websites with strong SEO requirements.

However, that's not a new concept. This architecture has been used for years with tools like Hotwire in Rails or Symfony applications.

Also, some of the issues that Server Components aim to address (like data fetching, partial rendering, etc) are already solved by some Single-Page Application frameworks, like our own react-admin. Other issues (large bundles, slow first load, SEO) aren't real problems for admin panels, SaaS, B2B apps, internal apps, CRMs, ERPs, long-lived apps, and more.

That's why many React developers, myself included, are happy with the Single-Page App architecture. And when I need to do some server-side rendering, I'll likely opt for a tool with a more mature ecosystem than React Server Components.

So if I don't need it, why should I be concerned?

The Standard Way To Build React Apps

My first problem is that React is dissuading people from using the Single-Page-App architecture. Or rather, they are discouraging developers from using React without a framework, and the frameworks they recommend promote server-side rendering.

Reect with no framework

I have a second problem.

The official React.js documentation primarily advises using Next.js.

The official Next.js documentation primarily advises using React Server Components starting from version 13.4.

In other words, React Server Components are the default way to build React apps as per the official documentation. A newcomer in the React ecosystem will naturally use them.

I think this is premature. In Dan Abramov's opinion, too:

"It takes a lot of work to make a new paradigm work"

React Server components require new generation routers, new generation bundlers. They are officially in alpha, and not ready for production.

So why is Next.js so pushy about it?

I can't avoid feeling that the new direction taken by Next.js is not designed to help developers, but to help Vercel sell React. You can't really sell a service around SPAs: once compiled, a SPA is a single JS file that can be hosted for free anywhere. But a server-side rendered app needs a server to run. And a server is a product that can be sold. Perhaps I'm a conspiracy theorist, but I don't see another reason to break the React ecosystem like this.

Existing Apps Are Not Affected

The introduction of React Server Components, unlike the Angular.js to Angular 2 transition, is not a breaking change. Existing single-page applications will still work with the latest version of React. Existing Next.js apps built with the Pages router will also work.

So, the answer to the question "Is React having an Angular.js moment?" is "No".

But if you start a new project today, what will you pick? A robust architecture with mature tooling and ecosystem (Single-Page Apps), or the new shiny thing that the React team is pushing (Server Components)? It's a difficult choice, and people might look for alternatives rather than risking the wrong choice.

I personally think that React's vision of a single tool to meet all web developer needs is overly ambitious - or the current solution is not the right one. To me, the proof is the dropdown in the Next.js documentation that lets readers choose between the App router (Server components) and the Pages router. If a single tool offers two very different ways to do the same thing, is it really the same tool?

Next.js doc

So to the question "Is React harming its community by being too ambitious", I think the answer is "Yes".

Conclusion

Server Components may represent progress for server-side frameworks - or at least, they could once they are ready for production. But for the broader React community, I believe they pose a risk of fragmentation and could jeopardize the momentum that React has been building over the years.

If I could express a wish, I'd like a more balanced approach from the React and Next.js teams. I'd like the React team to recognize that the Single-Page App architecture is a valid choice and that it's not going anywhere in the near future. I'd prefer to see Next.js downplay Server Components in their documentation, or at least mark it more prominently as an "alpha" feature.

Maybe I'm being grumpy (again) and it's the future. Or perhaps developers are destined to constantly shift back and forth between paradigms, and that's just the nature of the industry.

Did you like this article? Share it!