2.6.0 • Published 1 year ago

whatsup v2.6.0

Weekly downloads
9
License
MIT
Repository
github
Last release
1 year ago

npm.io

What is it?

Whats Up is a reactive framework. It is very easy to learn and powerful to work with. It has only a few components, but enough to build complex applications.

Install

npm i whatsup
# or
yarn add whatsup

Components

Observable

Creates a trackable field. Has .get and .set methods

import { observable } from 'whatsup'

const name = observable('Natali')

name.get() // 'Natali'
name.set('Aria')
name.get() // 'Aria'

Computed

Creates a derived field. Accepts a function or generator as an argument.

import { computed } from 'whatsup'

const firstName = observable('John')
const lastName = observale('Lennon')

const fullName = computed(() => {
    return `${firstName.get()} ${lastName.get()}`
})

Reaction

import { reaction } from 'whatsup'

const name = observable('Natali')
const dispose = reaction(
    () => name.get(),
    (name) => console.log(name)
)

//> 'Natali'

name.set('Aria')

//> 'Aria'

dispose() // to stop watching

Autorun

import { observable, autorun } from 'whatsup'

const name = observable('Natali')
const dispose = autorun(() => console.log(name.get()))

//> 'Natali'

name.set('Aria')

//> 'Aria'

dispose() // to stop watching

Observable Array

import { array, autorun } from 'whatsup'

const arr = array([1, 2])
const dispose = autorun(() => console.log(`Joined: ${arr.join()}`))

//> 'Joined: 1,2'

arr.push(3)

//> 'Joined: 1,2,3'

dispose() // to stop watching

Observable Set

import { set, autorun } from 'whatsup'

const mySet = set([1, 2])
const dispose = autorun(() => console.log(`My set has 3: ${mySet.has(3)}`))

//> 'My set has 3: false'

mySet.add(3)

//> 'My set has 3: true'

dispose() // to stop watching

Observable Map

import { map, autorun } from 'whatsup'

const myMap = set([
    [1, 'John'],
    [2, 'Barry'],
])
const dispose = autorun(() => {
    console.log(`My map has 3: ${myMap.has(3)}`)
    console.log(`Value of key 3: ${myMap.get(3)}`)
})

//> 'My map has 3: false'
//> 'Value of key 3: undefined'

myMap.set(3, 'Jessy')

//> 'My map has 3: true'
//> 'Value of key 3: Jessy'

myMap.set(3, 'Bob')

//> 'My map has 3: true'
//> 'Value of key 3: Bob'

dispose() // to stop watching

Use of generators

import { computed } from 'whatsup'

const firstName = observable('John')
const lastName = observale('Lennon')

const fullName = computed(function* () {
    while (true) {
        yield `${firstName.get()} ${lastName.get()}`
    }
})

Inside the generator, the keyword yield push data.

The life cycle consists of three steps:

  1. create a new iterator using a generator
  2. the iterator starts executing and stops after yield or return, during this operation, all .get() calls automatically establish the observed dependencies
  3. when changing dependencies, if in the previous step the data was obtained using yield, the work continues from the second step; if there was a return, the work continues from the first step

The return statement does the same as the yield statement, but it does the iterator reset.

Local-scoped variables & auto-dispose unnecessary dependencies

You can store ancillary data available from calculation to calculation directly in the generator body and you can react to disposing with the native language capabilities

import { observable, computed, autorun } from 'whatsup'

const timer = computed(function* () {
    // local scoped variables, they is alive while the timer is alive
    let timeoutId: number
    const value = observable(0)

    try {
        while (true) {
            const val = value.get()

            timeoutId = setTimeout(() => value.set(val + 1), 1000)

            yield val
        }
    } finally {
        // This block will always be executed when unsubscribing
        clearTimeout(timeoutId)
        console.log('Timer destroed')
    }
})

const useTimer = observable(true)

const app = computed(() => {
    if (useTimer.get()) {
        return timer.get()
    }
    return 'App: timer is not used'
})

autorun(() => console.log(app.get()))
//> 0
//> 1
//> 2
useTimer.set(false)
//> Timer destroed
//> App: timer is not used
useTimer.set(true) // the timer starts from the beginning
//> 0
//> 1
//> ...

Mutators

Allows you to create new data based on previous. You need just to implement the mutate method.

import { observable, computed, mutator } from 'whatsup'

const concat = (letter: string) => {
    return mutator((prev = '') => prev + letter)
}

const output = computed(function* () {
    const input = observable('')

    window.addEventListener('keypress', (e) => input.set(e.key))

    while (true) {
        yield concat(input.get())
    }
})

autorun(() => console.log(output.get()))

// bress 'a' > 'a'
// press 'b' > 'ab'
// press 'c' > 'abc'

Mutators as filters

Mutators can be used to write filters.

import { computed, reaction, Mutator } from 'whatsup'

const evenOnly = (next: number) => {
    // We allow the new value only if it is even,
    // otherwise we return the old value
    return mutator((prev = 0) => (next % 2 === 0 ? next : prev))
}

const app = computed(() => {
    return evenOnly(timer.get())
})

reaction(app, (data) => console.log(data))
//> 0
//> 2
//> 4
//> ...

You can create custom equality filters. For example, we want the computer to not run recalculations if the new list is shallow equal to the previous one.

If we use mobx we will do it like this:

const users = observable.array<User>([/*...*/])
const list = computed(() => users.filter(/*...*/), { equals: comparer.shallow })

Here is whatsup way:

const users = array<User>([/*...*/])
const list = computed(() => shallow(users.filter(/*...*/)))

And somewhere in the utilities

// ./utuls.ts

const shallow = <T>(arr: T[]) => {
    /*
    We have to compare the old and new value and 
    if they are equivalent return the old one, 
    otherwise return the new one.
    */
    return mutator((prev?: T[]) => {
        if (Array.isArray(prev) && prev.lenght === arr.length && prev.every((item, i) => item === arr[i])) {
            return prev
        }

        return arr
    })
}

Later we will collect the most necessary filters in a separate package.

Mutators & JSX

WhatsUp has its own plugin that converts jsx-tags into mutators calls. You can read the installation details here whatsup/babel-plugin-transform-jsx and whatsup/jsx

import { observable } from 'whatsup'
import { render } from '@whatsup/jsx'

function* Clicker() {
    const counter = observable(0)

    while (true) {
        const count = counter.get()

        yield (
            <div>
                <div>{count}</div>
                <button onClick={() => counter.set(count + 1)}>Clcik me</button>
            </div>
        )
    }
}

render(<Clicker />)
// Yes, we can render without a container, directly to the body

The mutator gets the old DOMNode and mutates it into a new DOMNode the shortest way.

Incremental & glitch-free computing

All dependencies are updated synchronously in a topological sequence without unnecessary calculations.

import { observable, computed, reaction } from 'whatsup'

const num = observable(1)
const evenOrOdd = computed(() => (num.get() % 2 === 0 ? 'even' : 'odd'))
const numInfo = computed(() => `${num.get()} is ${evenOrOdd.get()}`)

reaction(numInfo, (data) => console.log(data))
//> 1 is odd
num.set(2)
//> 2 is even
num.set(3)
//> 3 is odd
2.6.0

1 year ago

2.5.0

1 year ago

2.4.0

2 years ago

2.3.12

2 years ago

2.3.11

2 years ago

2.3.10

2 years ago

2.2.17

2 years ago

2.2.1

2 years ago

2.2.0

2 years ago

2.2.15

2 years ago

2.2.3

2 years ago

2.2.16

2 years ago

2.2.2

2 years ago

2.2.5

2 years ago

2.2.4

2 years ago

2.2.7

2 years ago

2.2.6

2 years ago

2.2.10

2 years ago

2.3.8

2 years ago

2.3.7

2 years ago

2.3.9

2 years ago

2.2.19

2 years ago

2.2.27

2 years ago

2.2.25

2 years ago

2.2.20

2 years ago

2.2.21

2 years ago

2.3.0

2 years ago

2.3.2

2 years ago

2.3.1

2 years ago

2.3.4

2 years ago

2.3.3

2 years ago

2.2.36

2 years ago

2.3.6

2 years ago

2.2.33

2 years ago

2.3.5

2 years ago

2.2.34

2 years ago

2.2.31

2 years ago

2.2.32

2 years ago

2.2.30

2 years ago

2.1.0

2 years ago

2.0.7

2 years ago

2.0.3

2 years ago

2.0.2

2 years ago

2.0.5

2 years ago

2.0.4

2 years ago

2.0.6

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.2.1

3 years ago

1.2.0

3 years ago

0.0.1-alpha.9

3 years ago

0.0.1-alpha.8

3 years ago

0.0.1-alpha.7

3 years ago

0.0.1-alpha.6

3 years ago

0.0.1-alpha.4

3 years ago

0.0.1-alpha.5

3 years ago

0.0.1-alpha.3

3 years ago

0.0.1-alpha.2

3 years ago

0.0.1-alpha.1

3 years ago

0.0.1-alpha.0

3 years ago

1.1.1

3 years ago

1.1.0

3 years ago

0.2.4-beta.1

3 years ago

0.2.4-beta.2

3 years ago

0.2.4-beta.0

3 years ago

0.2.3-prealpha.2

3 years ago

0.2.3-prealpha.1

3 years ago

0.2.3-share.0

3 years ago

0.2.3-async.1

3 years ago

0.2.3-async.0

3 years ago

0.2.3-defer.3

3 years ago

0.2.3-defer.2

3 years ago

1.0.1

10 years ago

1.0.0

10 years ago