Fine-Grained Reactivity Without Any Compiler

Rate this content
Bookmark

Achieving high-performance reactivity in React without compromising the developer experience has always been a key challenge. When building real-time applications designed to display hundreds of millions of rows without noticeable lag, fine-grained reactivity is essential. But when I started my journey at Pigment, neither Recoil, Jotai, nor Zustand were mature enough, and React Compiler was not yet a thing. Let's explore together how we managed to ingest, display, and update huge datasets seamlessly without sacrificing the developer or the user experience.

This talk has been presented at React Day Berlin 2024, check out the latest edition of this React Conference.

FAQ

Fine-grained reactivity refers to a system where updates are highly targeted and specific, occurring at a deeper level than just component level. In such systems, updates happen directly at the level of individual elements, like spans holding values, instead of re-rendering entire components.

Yes, React is considered reactive. When state changes occur, React components update to reflect those changes. However, React's reactivity is not as fine-grained, which means it might re-render more components than necessary for a small change.

Pigment faced challenges with React's reactivity when dealing with large grids of data and real-time updates. The company found that React re-rendered more components than necessary, which could lead to performance issues.

Pigment implemented a custom reactivity system on top of React. This involved creating hooks like useShell, useComputed, and useWatch, which allowed for more fine-grained updates and reduced unnecessary re-renders.

The useShell hook in Pigment's solution creates a 'shell' that holds a value and a setter function. This shell enables updates to happen without triggering React to re-render the entire component, allowing for more efficient state management.

Pigment chose to build a custom solution because existing libraries at the time did not fully meet their needs for fine-grained reactivity. Additionally, they wanted a system that would be easy for their developers to use and integrate with React.

Pigment's custom reactivity system allows for more efficient updates, reducing the number of unnecessary component re-renders. This improves performance, especially when dealing with large datasets and real-time data updates.

The main reason for optimizing their reactivity was to ensure that their application could handle large datasets and real-time updates efficiently, providing a smooth user experience without unnecessary lag or performance issues.

Yes, Pigment considered moving away from React and using native JavaScript for certain components due to performance issues. However, they chose to stay with React to maintain a consistent development environment and leverage React's capabilities.

The useWatch hook connects a shell to React, notifying React of changes in the shell's value. It uses React's useSyncExternalStore function to subscribe to updates and provide the current value to React, ensuring components update when necessary.

Nicolas Dubien
Nicolas Dubien
29 min
13 Dec, 2024

Comments

Sign in or register to post your comment.
Video Summary and Transcription
My name is Nicolas, and today, I'm going to talk about Fine-Grain Reactivity without the compiler this time. React is reactive, but it re-renders unnecessary components. Fine-grained reactivity is achieved by updating specific elements instead of whole components. Pigment encountered reactivity problems while building real-time financial data boards. They created their own reactive system on top of React to achieve fine-grained reactivity. They used custom hooks like useShell, useComputed, and useWatch to integrate React with their reactive system. They also considered using native JavaScript for optimization but chose to preserve React. They use external libraries like Zestand for flexibility, and maintaining reactivity during React updates has not been a problem.

1. Introduction to Fine-Grain Reactivity

Short description:

My name is Nicolas, and today, I'm going to talk about Fine-Grain Reactivity without the compiler this time. We first need to understand what is Reactivity about and is React reactive today? Let's see if React is reactive by considering a simple application made of three counters. To make it work properly, you need to move the states higher in the tree and pass props down to the counters and total. The app component is the main component in this application, made of three states passed to each counter. This is a basic app.

My name is Nicolas, and today, I'm going to talk about Fine-Grain Reactivity without the compiler this time. This is the last talk before the launch. After that, you will be able to lunch, so please stay.

But before we go into Fine-Grain Reactivity, we first need to understand what is Reactivity about and is React reactive today? In order to understand what is Reactivity about, I took this definition from the Vue ecosystem. When you modify the state, the Vue update, it makes state management simple and intuitive. We have another version of this one, which is just saying that the DOM or the Vue is a function of the state.

So the first question we should ask ourselves is, is React reactive or not? And in order to answer that question, a very simple thing to do is just to give it a try and to see if React is reactive. So in order to do that, I will consider this very simple application. It's made of three counters. So the first three lines are just counters. You can increment, decrement the counters, and you can see the value of the counter. At the end, you have the last line, and the last line is just the total of the three counters above.

If you were to build such application, you may come up with something like that, with three main components, one app component, one counter component, and a total. And obviously, within your app, you will use the counter component three times. But with that one, it doesn't work that well. You need to put some, I would say, interactivity for the users to be able to use your counters. So you will put some states directly within the counters, and with that, you are just able to play with each of the counters independently. You can increment, decrement your counters, and it will be fine for the users. But at some point, you need the total, and if you need the total, you need to know about the state of each of these counters directly within your total. And at this point, the usual trick is to move the states higher in the tree, and so we get the first three states directly in the app, and you just move the props down to the counters and to the total.

Now that we have everything, let's see how it works at the end. So let's see what is the code of this app component, because it is the main component in this whole application. So we'll just, this is like how your app component will be in that case. So my app component is made of three states. Each one has a value and a set value. And you can see that I pass the value plus the setter function directly to each of the counters. And at the end, you can see the total component, which is receiving the three values. This is a basic app.

On the right of the screen, you can see the app in action. So I'm just playing with this application.

2. Reactivity and the React Compiler

Short description:

I'm incrementing, decrementing counters, and you can see that everything works well. React is reactive, as the values in my state are correctly replicated on the screen. However, when I enable the React DevTools, I see that every time I increment or decrement a counter, not only that counter and the total are rendered, but also all the other counters and the app component. Recently, I tried using the compiler and noticed a slight difference. The counter and the total are rendered, but the app component is also rendered. I wanted to understand what the compiler was doing, so I checked the playground with the code.

I'm incrementing, decrementing counters, and you can see that everything works well. So React is reactive, because at the end, when I click on something, I can see that the values that I've installed in my state are just correctly replicated within the screen. I can see the total being updated, I can see the values being updated. But there is something I enable in the React DevTools that is just highlighting everything in the screen. Whenever I click somewhere, something is highlighted. Everything which is highlighted is around a rendering thing. So whenever React is rendering one of my components, you can see this small arrow stop around the component. It means that my function has been called again. And in my case, you can see that whenever I increment or decrement a counter, not only this counter plus the total has to be rendered again, but also all the other counters plus the app itself.

The first time I saw that, I was like, what is doing React? It's crazy. I mean, like, what the hell? I mean, I'm incrementing one counter, and everything got recomputed again and again. And I had to take it. I mean, it was OK. I went, maybe it's normal. Let's say it's normal. And recently, I've fallen under compiler stuff. I was like, yes. I will be saved. Maybe I will be saved this time. I enabled the compiler, again, the code I showed you. This time, we get something a bit different. When I increment the counter, we just see a render of this counter plus the total, obviously. But we also see a render of the app component. So it gets better. It's better, or at least, we'll say it's more intuitive. But as the compiler is doing a very strange thing, I wanted to get a look into what it was doing.

So I checked the playground with that piece of code. So if we toggle the playground of React compiler against that piece of code, we get the following more precisely, this code. I will not spend too much time in it. But there is a question.

QnA

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

SolidJS: Reactivity Unchained
JSNation 2022JSNation 2022
20 min
SolidJS: Reactivity Unchained
Solid.js is a declarative JavaScript library for building user interfaces that addresses performance optimization. It introduces fine-grained reactivity and avoids using a virtual DOM. The Talk explores rethinking performance and reactivity in web applications, understanding reactivity and primitives, and creating DOM elements and using JSX in Solid.js. It also covers rendering components, sharing state, and the advantages of fine-grained rendering and the reactive approach in Solid.js.
5 Years of Building React Table
React Summit 2022React Summit 2022
24 min
5 Years of Building React Table
Top Content
React Table is a popular table library that started with HTML5 tables and transitioned to React. It faced challenges with integration and user requests, leading to the development of React Table. The introduction of the Headless UI pattern and TypeScript support improved the library's capabilities and quality. Generics and TypeScript played a significant role in reducing the code size and improving development. React Table is now going framework agnostic and partnering with AG Grid.
Modern State Management with Vue 3
Vue.js London Live 2021Vue.js London Live 2021
22 min
Modern State Management with Vue 3
Top Content
Vanessa introduces Vue Free and discusses the benefits of using the Composition API. The order of execution and grouping logical units using the Composition API is explained. The Composition API is used for state management and refactoring components. The speaker shares their initial experience with state management using Vuex. Composables are explored as an alternative for state management in Vue 3.
Taking Vue.js to the Backend
Vue.js London Live 2021Vue.js London Live 2021
23 min
Taking Vue.js to the Backend
This talk explores using Vue.js in the backend, specifically focusing on Vue 3 Reactivity. It discusses how Vue 3 Reactivity leverages ES6 proxies to update changes and intercept hooks. The talk also covers implementing Vue.js backend with live demos, showcasing the modification of proxies and the use of reactive functions. It demonstrates the creation of a reactive array and the implementation of join, leave, and message functionalities. The talk concludes by mentioning the possibility of using computed properties and inviting further questions.
Why React Should Not Adopt Signals
React Advanced 2024React Advanced 2024
10 min
Why React Should Not Adopt Signals
Today I want to talk about why React should not adopt signals. By adopting signals in React, we can automatically track dependencies for effects and memos, leading to more efficient component rendering. Accessing specific parts of the JSX that read signals allows for fine-grained reactivity across component boundaries. Adopting signals in React requires migrating to single execution components, which only update specific parts of the application. This can become complex when dealing with components that read from different signals. In contrast, React assumes everything can change and enforces this assumption through linters and compilers, leading to more robust code and easier updates. If you're interested in signals in React or need performance improvements, let's chat!

Workshops on related topic

Build a Universal Reactive Data Library with Starbeam
JSNation 2023JSNation 2023
66 min
Build a Universal Reactive Data Library with Starbeam
WorkshopFree
Yehuda Katz
Yehuda Katz
This session will focus on Starbeam's universal building blocks. We'll use Starbeam to build a data library that works in multiple frameworks.We'll write a library that caches and updates data, and supports relationships, sorting and filtering.Rather than fetching data directly, it will work with asynchronously fetched data, including data fetched after initial render. Data fetched and updated through web sockets will also work well.All of these features will be reactive, of course.Imagine you filter your data by its title, and then you update the title of a record to match the filter: any output relying on the filtered data will update to reflect the updated filter.In 90 minutes, you'll build an awesome reactive data library and learn a powerful new tool for building reactive systems. The best part: the library works in any framework, even though you don't think about (or depend on) any framework when you built it.
Table of contents- Storing a Fetched Record in a Cell- Storing multiple records in a reactive Map- Reactive iteration is normal iteration- Reactive filtering is normal filtering- Fetching more records and updating the Map- Reactive sorting is normal sorting (is this getting a bit repetitive?)- Modelling cache invalidation as data- Bonus: reactive relationships