Deact is a web UI framework for the Dart language (and as you may guess) inspired by ReactJS. For the Dart ecosystem, there are already big players out there like Angular and Flutter. So why another web UI framework for the Dart language?

I used Angular with TypeScript for projects in my day job. I was never very satisfied with Angular because of its (IMHO) complex concepts and API.

Flutter is the rising star on the mobile platform and since 2019 it is possible to deploy a Flutter application to the web and even to the desktop. The “Everything is a widget” idea is great and Flutter is delivered with many high-quality widgets. But because Flutter supports many platforms (and for now, mobile as the primary one), it is not that deep integrated into the web platform. Thus, if you want to implement a web-only application and want to use web APIs, it may not be the best choice.

Last year I had the chance to learn ReactJS with the Hooks API in another day job project. I like its simple concepts and API from the first day of using it. There is a react package, but it seems, that it does not support the Hooks API.

So I had the idea to implement an UI framework with a simple API, without magic behind the scenes and that is native to the web and the Dart language.

Describing UI as code

I first started with evaluating an API to describe an UI in a declarative way. React’s JSX is a great way to declare an UI. But Dart does not support JSX and bringing JSX support to the Dart language is way too much effort for a single developer. However, Flutter proofs that the Dart language works very well to implement UI as code. Especially with language features like named parameters as well as collection if, collection for and the spread operator introduced with Dart 2.3.

The only thing missing is an API to describe DOM elements with its attributes and children. Thus, I generated an API from the HTML specifications. Here, you see a short HTML snippet:

And this is how it looks like using Deact:

Both snippets have 4 lines of code, the lines of the Deact snippet are a little bit longer. But I think, it is anyway easy to read.

Everything is a node

The functions div() and span() return an instance of the class ElementNode. The function txt() returns an instance of the class TextNode. Both classes are subtypes of the class DeactNode.

In Deact, a UI is built of an hierarchy of these nodes. To represent this hierarchy, every node can have a list child nodes. But not every subtype of DeactNode allows you to add child nodes, because it makes no sense in all cases (e.g. TextNode).

There are to two more types of nodes in Deact: FragmentNode and ComponentNode. A fragment node is a helper to wrap a list of nodes in a single node. You can use it everywhere, where a single node is expected, but you want to provide a list of nodes.

Last but not least, let’s talk about components.

Adding reusability with components

Don’t Repeat Yourself (or DRY) is a common principle to improve the quality of source code. Thus, all UI frameworks have concepts to reuse chunks of the UI. They give them just different names.

I call them components because I think, it is the most used term. In its simplest form, a Deact component is just a function that returns a node. Look at the example below for a refactored version of the last example.

We created a simple component coloredText to reuse UI code. We no longer repeat ourselves by calling the span() function with nearly identical parameters twice. No additional Deact API is required for that simple kind of a component.

Yes, we added many more lines, but as a component will grow the tides turn over.

Creating full-featured components

Deact provides a set of features to components like state, effects and references. As I wrote above, I like the Hooks API and the way how to write full-featured components with this API. Therefore, Deact provides a similar API, but with some differences.

Deact provides two ways to create a component with access to component-specific features. A class-based and a functional way. Both ways provides the same set of features. So it is only a matter of taste, which way to use.

To better understand the functional way, I will first explain how the class-based way and how a ComponentNode works. A ComponentNode is implemented like this:

If you extend this class, you have to implement the render() method. If Deact renders a component to the DOM the result of this function is applied. The parameter context is the thing that gives you the access to the component-specific features. The implementation of a class-based component would look like this:

Creating a subclass of ComponentNode by yourself is the class-based way. Now, lets take a look of how to create the same component in a functional way:

The function fc() of the Deact API is used to create a functional component. This function takes a FunctionalComponent as parameter and returns a DeactNode. FunctionalComponent is a typedef for a function, that takes a ComponentRenderContext and a returns a DeactNode. This is the same signature as the render() method of a ComponentNode.

Under the hood, the function fc() creates and returns an instance of the class Functional, which is a subtype of ComponentNode. The class Functional stores the FunctionalComponent provided to the fc() call. When the render() method of Functional is called, the call is delegated to the stored FunctionalComponent.

Accessing component-specific features

Let’s see how to access the component-specific features. If you have studied the last two code snippets in detail, you will have noticed, that the state feature is used. The state of a component persist the whole lifetime (from the time when the component is added to the node hierarchy until the moment the component is removed) of the component. You access a state by calling the method state() of ComponentRenderContext. The method requires two parameters to be provided, a name and an initial value. The function returns an instance of type State. If a component has not already introduced the state, the state is created and initialized with the initial value.

You can access the value of a state by using the getter value. To update the value, you have multiple options. In the example above, the method set() is used. No matter which option you use, after the new value is set, Deact re-renders the component and the nodes beneath that component.

Why does a state have a name?

When using the React Hooks API, you have to learn two rules:

Only call hooks at the top level Only call hooks from React functions

You can find more information about these rules here. The rules are not enforced by React’s API. Thus, as a developer, you always must have them in mind.

Deact tries to enforce/obsolete these rules by its API. This is why Deact gives states a name. By doing so, it is not dangerous to use a state in a conditional block or inside a loop, which makes rule 1 obsolete.

The second rule is enforced by provide access to component-specific features using the interface ComponentRenderContext, which is only provided inside a component, which makes it hard for a developer to use these features from outside a component.

More component-specific features

Deact provides two more features to components not discussed in detail in this article.

The first one are references. A reference is very similar to a state. You can store a value in it. But if you update the value of a reference, Deact does not re-render the node hierarchy. But it is possible to register a change listener to a reference.

The second feature are effects. An effect is a piece of code to be executed after a component is (re-)rendered. You have some options to configure an effect to achieve different behaviours:

Only execute the effect when the component is added to the node hierarchy. Only execute the effect when the value of a state has changed. Execute the effect every time the component is re-rendered.

Additionally, an effect can have a cleanup function.

Some internal details

As written above, a goal of Deact is a framework without magic in it. The Deact core is written with only 676 lines of code (including comments). Not much space for magic, or is it?

The API to create element nodes has about 30k lines of code, but it is fully generated.

Deact heavily depends on the Incremental DOM library from Google to update the DOM. I have published a package to use this library from the Dart language.

Disclaimer

This article is based on version 0.3.1 of Deact. This version (and newer ones) are published on pub.dev and the sources are available at Github.

Deact is a One-Man-Show. Thus, don’t expect too much progress in short time. Currently, I proof Deact in an internal project. Features will be updated/added based on experiences made in that project.

If you will give Deact a try, I would be very happy about some feedback!