WhatsUp - front-end framework based on ideas of streams and fractals. Part 2.
Tags: webdev, typescript, javascript
Published: 2021-04-05 14:47:38

Hi guys!

My name is Dan. This is the second article about my project. 

In the [first](https://dev.to/iminside/whatsup-front-end-framework-based-on-ideas-of-streams-and-fractals-4cke) article I talked about how state management can be organized using WhatsUp. Today I will tell you how easy and simple it is to organize the display of data in a react-like way - with jsx.

## JSX

WhatsUp has its own jsx-renderer. There are two packages for this - [`@whatsup/jsx`](https://github.com/whatsup/jsx) & [`@whatsup/babel-plugin-transform-jsx`](https://github.com/whatsup/babel-plugin-transform-jsx). And a simple HelloWorld would look like this.

```typescript
import { cause } from 'whatsup'
import { render } from '@whatsup/jsx'

const app = cause(function*(){
    while(true){
        yield <div>Hello world</div>
    }
})

render(app)
```
[Example](https://codesandbox.io/s/whatsup-jsx-hello-world-4yg9p)

His work is based on mutators. Unlike React, WhatsUp never creates the entire virtual house as a whole for further comparison and modification. It incrementally modifies the thread-specific fragment. In addition, WhatsUp can render directly to the body element (by default).

Fragments and their syntax (`<></>`) are also supported, and components are only defined as pure functions, have no internal state, and are only responsible for markup.

## Fractals

This is perhaps the main component of my framework. Looking for inspiration, I looked on YouTube a lot of videos of traveling to fractals, for example [one](https://www.youtube.com/watch?v=fFyC68CIEio), [two](https://www.youtube.com/watch?v=8cgp2WNNKmQ), [three](https://www.youtube.com/watch?v=ZQFwzyFsElA). Just take a look. Imagine that you are flying through the universe in a spaceship. It's incredibly beautiful!

There is mathematics behind all this beauty. If you look for a long time, you will notice some patterns. The fractal is designed in such a way that if we do not pay attention, then we do not notice the seams between the repeating patterns. They are not the same repeating pattern. Each time this is a new pattern, created in the context of the previous one according to the same rules.

Now imagine that our ship is not moving, it stands still, and the fractal gives us a stream of information towards us. This information reflects the internal state of the fractal. I really hope that I managed to describe it correctly. Let's move on to practice.

```typescript
const app = fractal(function*(){
    while(true){
       yield <div>Hello world</div>
    }
})
```

It looks like `Cause`, right? This is true, the only difference is that for each consumer, the fractal creates a personal generator and context. The contexts connect to each other as parent-child and form a context tree. This allows you to organize the transfer of data down the tree, as well as the bubbling of events up. A cause, unlike a fractal, creates one iterator for all consumers, and one context without a reference to the parent (root context).

### Sharing

This mechanism allows you to move data down the context. Any data shared somewhere in parent fractals will be available in child fractals. To do this, in the context there are methods `ctx.share()`, `ctx.get()` and `ctx.find()`.

```typescript
import { fractal } from 'whatsup'
import { render } from '@whatsup/jsx'

class Theme {
    readonly name: string
    
    constructor(name: string) {
        this.name = name
    }
}

const app = fractal(function* (ctx) {
    const theme = new Theme('dark')

    ctx.share(theme)
    // share theme instance to children fractals

    while (true) {
        yield <div>{yield* page}</div>
    }
})

const page = fractal(function* (ctx) {
    const theme = ctx.get(Theme)
    // get theme instance from parent fractals

    while (true) {
        yield (
            <div>
                Theme name is <b>{theme.name}</b>
            </div>
        )
    }
})

render(app)
```
[Example](https://codesandbox.io/s/whatsup-context-sharing-wm4s8)

The `ctx.get()` method searches for data based on a strict constructor match, but `ctx.find()` method uses `instanceof`.

```typescript
class User {}
class Guest extends User {}

const guest = new Guest()

// then in parent fractal
ctx.share(guest)

// and in child fractal
const user = ctx.find(User)
//    ^^^^
// the user constant will contain the Guest instance
```

There is another way to share data - factors. 

```typescript
import { factor } from 'whatsup' 

const Token = factor(null)
//     default value ^^^^ if factor is not found 

// then in parent fractal
ctx.share(Token, 'i am a secret token')

// and in child fractal
const token = ctx.get(Token)
//> 'i am a secret token'
```

A factor is a key by which you can share data in a context and get data out of a context.

### Event system

This system allows sending events between fractals up the context. To work with events in context, there are `ctx.on()`,`ctx.off()` and `ctx.dispatch()` methods.

```typescript
import { fractal, Event } from 'whatsup'
import { render } from '@whatsup/jsx'

class TimeEvent extends Event {
    readonly time: number

    constructor(time: number) {
        super()
        this.time = time
    }
}

const app = fractal(function* (ctx) {
    ctx.on(TimeEvent, (e) => console.log('Time', e.time))
    // attach listener for TimeEvent instances

    while (true) {
        yield <div>{yield* page}</div>
    }
})

const page = fractal(function* (ctx) {
    const onClick = () => {
        const event = new TimeEvent(Date.now())
        ctx.dispath(event)
        // trigger event bubbling
    }

    while (true) {
        yield (
            <div>
                <button onClick={onClick}>Print time</button>
            </div>
        )
    }
})

render(app)
```

[Example](https://codesandbox.io/s/whatsup-event-system-1semz)

Look at an [example](https://codesandbox.io/s/whatsup-event-system-1semz) - when you click on the button, a message with the current time will appear in the console.

The `ctx.off()` method is needed to remove the event handler. In most cases, it does not need to be called manually - all handlers are automatically removed when the fractal is destroyed.

### Extended example

The `fractal` function is shorthand for creating fractals. There is a base class `Fractal` available for extension. When extending, we need to implement a `whatsUp` method that returns a generator.

The following example demonstrates the use of the event system and data sharing.

```typescript
import { Fractal, Conse, Event, Context } from 'whatsup'
import { render } from '@whatsup/jsx'

class Theme extends Conse<string> {}

class ChangeThemeEvent extends Event {
    readonly name: string

    constructor(name: string) {
        super()
        this.name = name
    }
}

class App extends Fractal<JSX.Element> {
    readonly theme = new Theme('light');
    readonly settings = new Settings()

    *whatsUp(ctx: Context) {
        // sharing this.theme for child fractals
        ctx.share(this.theme)

        // attach ChangeThemeEvent event listener
        ctx.on(ChangeThemeEvent, (e) => this.theme.set(e.name))

        while (true) {
            yield (<div>{yield* this.settings}</div>)
        }
    }
}

class Settings extends Fractal<JSX.Element> {
    *whatsUp(ctx: Context) {
        // get Theme from context
        const theme = ctx.get(Theme)
        // dispatch ChangeThemeEvent on click button
        const change = (name: string) => 
            ctx.dispatch(new ChangeThemeEvent(name))

        while (true) {
            yield (
                <div>
                    <h1>Current</h1>
                    <span>{yield* theme}</span>
                    <h1>Choose</h1>
                    <button onClick={() => change('light')}>light</button>
                    <button onClick={() => change('dark')}>dark</button>
                </div>
            )
        }
    }
}

const app = new App()

render(app)
```
[Example](https://codesandbox.io/s/whatsup-events-and-sharing-2rino)

## Errors catching

Throwing an exception at the framework level is common. The error propagates along the streams, like any other data, and is handled by the standard `try {} catch {}` construction. In this case, the reactivity system preserves the state of dependencies in such a way that when the situation is corrected and the error disappears, the threads recalculate their data and return to their normal operating state.

```typescript
import { conse, Fractal } from 'whatsup'
import { render } from '@whatsup/jsx'

class CounterMoreThan10Error extends Error {}

class App extends Fractal<JSX.Element> {
    *whatsUp() {
        const clicker = new Clicker()
        const reset = () => clicker.reset()

        while (true) {
            try {
                yield <div>{yield* clicker}</div>
            } catch (e) {
                // handle error  
                if (e instanceof CounterMoreThan10Error) {
                    yield (
                        <div>
                            <div>Counter more than 10, need reset</div>
                            <button onClick={reset}>Reset</button>
                        </div>
                    )
                } else {
                    throw e
                }
            }
        }
    }
}

class Clicker extends Fractal<JSX.Element> {
    readonly count = conse(0)

    reset() {
        this.count.set(0)
    }

    increment() {
        const value = this.count.get() + 1
        this.count.set(value)
    }

    *whatsUp() {
        while (true) {
            const count = yield* this.count

            if (count > 10) {
                throw new CounterMoreThan10Error()
            }

            yield (
                <div>
                    <div>Count: {count}</div>
                    <button onClick={() => this.increment()}>increment</button>
                </div>
            )
        }
    }
}

const app = new App()

render(app)
```
[Example](https://codesandbox.io/s/whatsup-error-catching-fljz9)

I tried to design the framework in such a way that it was easy and intuitive to use it.

## Performance

Performance is very important. I added whatsapp to the [krausest/js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) project. Below is an excerpt from the results table showing WhatsUp position against the background of the most popular libraries and frameworks such as Inferno, Preact, Vue, React and Angular.

![](https://habrastorage.org/webt/jt/y6/sd/jty6sdnkq_dmohr2dzpxaornse4.jpeg)

In my opinion, the position is already quite worthy.

## Conclusion

I hope you enjoyed the article. These are not all the features of the WhatsUp framework. In the next article I will talk about delegation, routing, asynchronous tasks. You can find more examples [here](https://whatsup.github.io/), their [sources](https://github.com/whatsup/whatsup.github.io/tree/master/src/root).

If you liked the idea of my framework - leave your feedback or a star on the github, also contributing welcomed. I'll be very happy. Thanks!

{% github whatsup/whatsup no-readme %}