I wanted to improve React
Tags: react, solidjs, javascript, typescript
Published: 2023-05-05 07:57:19

## Intro

I have been coding for a long time, and I have been using React for over five years.

During this time, I have had several ideas on how React could be improved.

About three years ago, I started working on implementing these ideas.
First, I tested the concepts, and then decided to turn everything into a library.

In this article, I would like to tell you about what came out of it.

## What is good about React

Firstly, this is the approach of React in combining Javascript and HTML in one code. Other frameworks did not manage to do this as well. For example, some frameworks invented their own template programming languages, which duplicate the constructions of the JavaScript language, such as `if`, `else`, `each`...

In my opinion, this is an unnecessary cognitive load. Fortunately, some frameworks have since acquired support for JSX.

Another important but not so obvious feature of React is the one-way data flow and the corresponding mental model, which helps to structure the code while remaining flexible.

## What is wrong with React

### Component creation

In React, it is recommended to use *functional* components instead of class-based ones. This certainly makes the work easier.

Let's consider the following example:

```jsx
function CounterButton() {
  const [count, setCount] = useState(0);
  const handleClick = () => setCount(count + 1);
  return (
    <button onClick={handleClick}>
      You clicked {count} times
    </button>
  );
}
```

The `CounterButton` function is both a component *constructor* and a function that updates data. Such a mix of responsibilities is a bad practice, and it creates many problems, which we will talk about now.

### Updating components

React calls the `CounterButton` function every time data is updated. All objects created inside this function will be recreated every time it is called.

In the example, the `handleClick` function is such an object. Resources could be saved if this function were created once in the constructor. But it does not exist in *functional* components.

Also, the `CounterButton` function returns a new virtual DOM object every time it is called.

> New objects are created on the [heap](https://en.wikibooks.org/wiki/Memory_Management/Stacks_and_Heaps), and old objects are removed by the [garbage collector](https://en.wikibooks.org/wiki/Memory_Management/Garbage_Collection). This process causes fragmentation and excessive memory usage. Periodic [defragmentation](https://en.wikibooks.org/wiki/Memory_Management/Memory_Compacting) is also required. All of these processes are resource-intensive.

It is worth noting that in our specific example, this aspect of React's behavior does not matter. But for large applications with hundreds or thousands of components, it becomes noticeable.

That is why the React team, starting with version 17, began developing [concurrent mode](https://17.reactjs.org/docs/concurrent-mode-intro.html) to allow updating the UI while React processes are running.

### Hooks

One could argue that using *hooks* would partially solve the issue of creating unnecessary objects.

> By the way, in one company where I worked, the policy was to always use *hooks*.

So here is an example:

```javascript
const handleClick = useCallback(
  () => setCount(count + 1),
  [count]
);
```

On one hand, *hooks* allow you to forget about unnecessary updates of components located lower in the tree. But on the other hand, they do not solve the problem of creating "garbage" objects. For instance, the function `() => setCount(count + 1)` is created only to be discarded by the *hook* if `count` has not changed. Moreover, a new array object `[count]` is also created.

The same thing happens with other *hooks* as well. For example, compare `constructor(){code();}` and `useEffect(() => {code();}, [])`. In the first case, the code will run only once in the constructor. In the second case, two extra objects will be created on each update.

Also, the *hooks* mechanism itself is additional logic that affects performance. But the biggest drawback of *hooks*, in my opinion, is their verbosity. It is incredibly boring to write wrappers for simple operations. And the readability of the code suffers as well.

### In conclusion

- Many unnecessary objects are created and deleted on the *heap*, causing fragmentation, memory overconsumption, and defragmentation, which deteriorates performance.
- More logic is introduced for the *concurrent mode*, which also worsens performance.
- Additional logic of the use of *hooks* also worsens performance.
- *Hooks* make the code more verbose and harder to understand.

## How to Improve

Next, I would like to propose solutions to the aforementioned problems.

So, in short, we need to:

- Improve performance.
- Reduce verbosity.
- Make the code more explicit and easy to understand.

### Component Constructor

Let's start with a hypothetical example:

```javascript
function CounterButton() {
  let count = 0;
  const handleClick = () => {
    count++;
    btn.update();
  };
  const btn = button(
    { click$e: handleClick }, // props
    () => `You clicked ${count} times`, // child
  );
  return btn;
}
```

It looks very similar to a React example. For simplicity, let's omit JSX for now.

The only unknown factor here is the `button` function, where all the "magic" happens. We can imagine how it might work based on our example.

The `button` function should perform the following actions:

- Create a DOM object `HTMLButtonElement`.
- Set a click event handler `handleClick` for the button.
- Set the text of the button using the value returned by the lambda function ``` () => `You clicked ${count} times` ```.
- Allow updating the text of the button using the result of the lambda function.

It makes sense that the `button` function should create and return an object with two properties: `element` and `update`.

The class for this object could look like this:

```javascript
class Component {
  get element() {}; // return DOM Element object
  update() {}; // update dynamic data
}
```

When the button is clicked, the counter is increased by one `count++`. Then the `btn.update()` method is called, which executes the lambda function and updates the button text.

### Using the component

Now let's attach this component to the DOM tree:

```javascript
document.body.append(
  CounterButton().element,
);
```

First, we call the `CounterButton` function, which creates and returns the component, and then attach its element to the DOM tree.

Now, the button with a counter should be displayed and correctly count the number of clicks.

Okay, let's suppose that:

- In addition to `button`, there is a full set of HTML functions: `h1`, `div`, `span`, and so on...
- Components created by these functions can contain *child* components, and so on to infinity.
- When the *parent* component is updated, its *child* components will be updated as well.

That's it! 🤗

This approach solves all the aforementioned problems and preserves the good features of React.

### Result

I bet you expected something bigger.

Do not worry that component updates are made explicitly. Mostly, updates are triggered by *higher-order* components. However, it is also possible to update any part selectively.

> By the way, in React, explicit updates also need to be called. The `setState` function and `useState` *hook* serve this purpose. However, it is less flexible and more resource-intensive. For example, calling `setCount(count + 1)` will set the variable through the state mechanism, and then add the need for an update to the queue through the update mechanism. As you can see, there is again a mixing of responsibilities.

Thus, the aforementioned concept helps to solve problems in the following way:

- Firstly, the component creation function serves as its constructor and is called only once. Accordingly, objects created inside the function are also created only once and do not require such *hacks* as *hooks*.
- There are no *hooks*. There is no additional logic. Everything happens explicitly and readably.
- *Concurrent mode* is not necessary, as firstly, the amount of logic has significantly decreased, and secondly, we have complete control over the processes of creation and update, and can easily insert interface rendering where needed.

## Fusor

### What it is

*Fusor is a simple library that helps declaratively create and update DOM elements.*

In Fusor, there are no additional mechanisms for:

- *Props*
- *State*
- *Context*
- *Lifecycle*

Fusor is a minimalistic and transparent approach that uses the constructs of the Javascript language and DOM functions "almost without a library".

Nevertheless, Fusor can fully replace React! How is that possible? Let's take a closer look.

### Economy

*Fusor is an economical library.*

Fusor does not create a heap of unnecessary objects on the *heap*.

For example, let's consider the following code:

```javascript
import { div, p } from '@fusorjs/dom/html';
const wrapper = div(
  p('I am the static text')
);
```

The variable `wrapper` will contain an `HTMLDivElement` object, not a `Component` as in the example with the counter button, since there are no *dynamic* parts here.

If we take the example with the button and modify it slightly:

```javascript
import { button } from '@fusorjs/dom/html';
function CounterButton() {
  let count = 0;
  const handleClick = () => {
    count++;
    btn.update();
  };
  const btn = button(
    // props:
    { click$e: handleClick }, 

    // child text nodes:
    'You clicked ', // static
    () => count, // dynamic 
    ' times', // static
  );
  return btn;
}
```

So you can see that now only one of the three *child* elements of the button is *dynamic*. And the `btn` variable will be an object of the `Component` class.

When updating, only the value of one text *node*, to which the lambda function `() => count` is bound, will be changed if the value is different from what is already there.

Thus, an additional component object is only created if it contains *dynamic* data.

*Dynamic* data can also be in properties. For example, `{class: () => selected ? 'selected' : 'unselected'}`.

### Lifecycle

Component lifecycle is the only mechanism that Fusor lacks to be able to replace React.

Since Fusor does one thing and does it well, it does not have a component lifecycle logic. However, such logic exists in native [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#using_the_lifecycle_callbacks).

Fusor fully supports all web standards, including *web components*. Therefore, they can be used to connect lifecycle events.

Nevertheless, for convenience, Fusor has a *custom element* called `fusor-life` and its wrapper component `Life`:

```javascript
import { Life } from '@fusorjs/dom/life';
const wrapper = Life(
  {
    connected$e: () => {},
    disconnected$e: () => {},
    // ... other props
  },
  // ... children
);
```

Compare this to the React component lifecycle mechanism and the `O(n)` traversal of the component tree.

|            | Fusor       | fusor-life       | React                                                                                                |
| ---------- | ----------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
| Mounting   | constructor | connected        | constructor, getDerivedStateFromProps, render, componentDidMount                                     |
| Updating   | update      | attributeChanged | getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate |
| Unmounting |             | disconnected     | componentWillUnmount                                                                                 |

### Customization

It is not necessary to use functions located in `html`, `svg`, or `life`. They exist to avoid having to create them manually and to demonstrate how it is done.

For example, if you need to create a specific set of HTML tags, you can easily do so.

If you need to use a single function for all elements, you can use the `h` function for HTML or the `s` function for SVG. For example: `h('div', props, children)`. Or you can make other variations.

There is also a more flexible function `create(element, props, children)`. Using this function, you can configure the use of JSX with Fusor.

### About JSX

JSX support will also be available.

*Functional notation* is also good because:

- It is pure JavaScript with regular comments.
- No conversion, build, or compilation is required.
- You can use any number of *props* and *children* in any order.

### Links

Full-fledged applications and other resources:

- A [counter button](https://codesandbox.io/s/fusor-intro-cvbhsk?file=/src/index.js) - an interactive example of an application.
- A [tutorial with recipes](https://fusorjs.github.io/tutorial/) ([code](https://github.com/fusorjs/tutorial)) - an interactive application with the main usage scenarios of Fusor: lifecycle, request, routing, etc.
- [Implementation of TodoMVC](https://fusorjs.github.io/todomvc/) ([code](https://github.com/fusorjs/todomvc)) - an application through which Fusor was developed. Therefore, it turned out not to be the easiest and most beautiful, but ideologically correct. Also, no lifecycle events are needed in this application.
- [Fusor repository and documentation](https://github.com/fusorjs/dom).

## Conclusion

There are applications. Examples of basic usage scenarios are available. Test coverage is also available.

The API has been stable for quite some time. Fusor can be used in production.

```bash
npm install @fusorjs/dom
```

PS: Thank you to everyone who made it to the end! 🤗 ❤️

## Fusor vs React

|                       | Fusor                   | React                                           |
| --------------------- | ----------------------- | ----------------------------------------------- |
| Component constructor | Explicit, function      | Combined with updater in funtion components     |
| Objects in Component  | Created once            | Re-created on each update even with memoization |
| State, effects, refs  | Variables and functions | Complex, hooks subsystem, verbose               |
| Updating components   | Explicit, flexible      | Implicit, complex, diffing                      |
| DOM                   | Real                    | Virtual                                         |
| Events                | Native                  | Synthetic                                       |
| Life-cycle            | Native, custom elements | Complex, tree walking                           |