DEV Community

Discussion on: You probably don't need Redux: Use React Context + useReducer hook

Collapse
 
artydev profile image
artydev • Edited

Hello, look at the Meiosis pattern.
Regards
Search Meiosis on Dev to

Collapse
 
leob profile image
leob • Edited

First time I hear about it: dev.to/artydev/frontend-state-mana...

The explanations are terse, but yes I saw this:

meiosis.js.org/

"Think Redux, MobX, Cerebral, React Context, etc. but without having library imports everywhere and being tied to a framework API. Instead, just plain functions and objects. All you need is a simple stream library such as Flyd or Mithril Stream"

So you don't need to have library imports ... but you do need a library ;)

Collapse
 
artydev profile image
artydev • Edited

No :-)
All you need is two streams functions map and scan that you can implement yourself or use the simple implementation mithril-stream

And the function I use to merge states is a thirty lines of code called mergerino, which extends Object.assign.

I would not call them library, they are so tiny and very specialised

You can use whathever you want for the view (event document.innerHTML)

Here I use JQuery
MeiosisJS

Look at this for a starting pont :
observables

Regards

code for mergerino

const assign = Object.assign || ((a, b) => (b && Object.keys(b).forEach(k => (a[k] = b[k])), a))

const run = (isArr, copy, patch) => {
  const type = typeof patch
  if (patch && type === 'object') {
    if (Array.isArray(patch)) for (const p of patch) copy = run(isArr, copy, p)
    else {
      for (const k of Object.keys(patch)) {
        const val = patch[k]
        if (typeof val === 'function') copy[k] = val(copy[k], merge)
        else if (val === undefined) isArr && !isNaN(k) ? copy.splice(k, 1) : delete copy[k]
        else if (val === null || typeof val !== 'object' || Array.isArray(val)) copy[k] = val
        else if (typeof copy[k] === 'object') copy[k] = val === copy[k] ? val : merge(copy[k], val)
        else copy[k] = run(false, {}, val)
      }
    }
  } else if (type === 'function') copy = patch(copy, merge)
  return copy
}

const merge = (source, ...patches) => {
  const isArr = Array.isArray(source)
  return run(isArr, isArr ? source.slice() : assign({}, source), patches)
}

export default merge

the code of mithril-stream is here from Isiah Meadows Mithril's maintener.

/* eslint-disable */
;(function() {
"use strict"
/* eslint-enable */
Stream.SKIP = {}
Stream.lift = lift
Stream.scan = scan
Stream.merge = merge
Stream.combine = combine
Stream.scanMerge = scanMerge
Stream["fantasy-land/of"] = Stream

var warnedHalt = false
Object.defineProperty(Stream, "HALT", {
    get: function() {
        warnedHalt || console.log("HALT is deprecated and has been renamed to SKIP");
        warnedHalt = true
        return Stream.SKIP
    }
})

function Stream(value) {
    var dependentStreams = []
    var dependentFns = []

    function stream(v) {
        if (arguments.length && v !== Stream.SKIP) {
            value = v
            if (open(stream)) {
                stream._changing()
                stream._state = "active"
                dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) })
            }
        }

        return value
    }

    stream.constructor = Stream
    stream._state = arguments.length && value !== Stream.SKIP ? "active" : "pending"
    stream._parents = []

    stream._changing = function() {
        if (open(stream)) stream._state = "changing"
        dependentStreams.forEach(function(s) {
            s._changing()
        })
    }

    stream._map = function(fn, ignoreInitial) {
        var target = ignoreInitial ? Stream() : Stream(fn(value))
        target._parents.push(stream)
        dependentStreams.push(target)
        dependentFns.push(fn)
        return target
    }

    stream.map = function(fn) {
        return stream._map(fn, stream._state !== "active")
    }

    var end
    function createEnd() {
        end = Stream()
        end.map(function(value) {
            if (value === true) {
                stream._parents.forEach(function (p) {p._unregisterChild(stream)})
                stream._state = "ended"
                stream._parents.length = dependentStreams.length = dependentFns.length = 0
            }
            return value
        })
        return end
    }

    stream.toJSON = function() { return value != null && typeof value.toJSON === "function" ? value.toJSON() : value }

    stream["fantasy-land/map"] = stream.map
    stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) }

    stream._unregisterChild = function(child) {
        var childIndex = dependentStreams.indexOf(child)
        if (childIndex !== -1) {
            dependentStreams.splice(childIndex, 1)
            dependentFns.splice(childIndex, 1)
        }
    }

    Object.defineProperty(stream, "end", {
        get: function() { return end || createEnd() }
    })

    return stream
}

function combine(fn, streams) {
    var ready = streams.every(function(s) {
        if (s.constructor !== Stream)
            throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream")
        return s._state === "active"
    })
    var stream = ready
        ? Stream(fn.apply(null, streams.concat([streams])))
        : Stream()

    var changed = []

    var mappers = streams.map(function(s) {
        return s._map(function(value) {
            changed.push(s)
            if (ready || streams.every(function(s) { return s._state !== "pending" })) {
                ready = true
                stream(fn.apply(null, streams.concat([changed])))
                changed = []
            }
            return value
        }, true)
    })

    var endStream = stream.end.map(function(value) {
        if (value === true) {
            mappers.forEach(function(mapper) { mapper.end(true) })
            endStream.end(true)
        }
        return undefined
    })

    return stream
}

function merge(streams) {
    return combine(function() { return streams.map(function(s) { return s() }) }, streams)
}

function scan(fn, acc, origin) {
    var stream = origin.map(function(v) {
        var next = fn(acc, v)
        if (next !== Stream.SKIP) acc = next
        return next
    })
    stream(acc)
    return stream
}

function scanMerge(tuples, seed) {
    var streams = tuples.map(function(tuple) { return tuple[0] })

    var stream = combine(function() {
        var changed = arguments[arguments.length - 1]
        streams.forEach(function(stream, i) {
            if (changed.indexOf(stream) > -1)
                seed = tuples[i][1](seed, stream())
        })

        return seed
    }, streams)

    stream(seed)

    return stream
}

function lift() {
    var fn = arguments[0]
    var streams = Array.prototype.slice.call(arguments, 1)
    return merge(streams).map(function(streams) {
        return fn.apply(undefined, streams)
    })
}

function open(s) {
    return s._state === "pending" || s._state === "active" || s._state === "changing"
}

if (typeof module !== "undefined") module["exports"] = Stream
else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = Stream
else window.m = {stream : Stream}

}());
Thread Thread
 
leob profile image
leob

So you need to learn how think in terms of streams and observables (I think RxJS is a famous library in this area) ... if you're not familiar with that paradigm yet then it's definitely a learning curve compared to the way you'd normally do things with Redux or Context.

Thread Thread
 
artydev profile image
artydev • Edited

Think of streams as Excel cells and you will have a good starting point.
I have written a few lines as introduction here

streams

Thread Thread
 
artydev profile image
artydev • Edited

You absolutely don't need RxJS, as I said earlier you only need map and scan for Meiosis

Here is very simple stream implementaion with a map function :

function Stream (value) {
  let storedvalue = value
  let mappers = []
  function stream (newvalue) {
    if (arguments.length) {
      mappers.map(f => f(newvalue))
      storedvalue = newvalue
    }
    return storedvalue
  }
  stream.map = function (f) {
    mappers.push(f)
  }
  return stream
}

s = Stream()

document.addEventListener("mousemove", s)

s.map(t => divmouse.innerHTML = (`<h2>(${t.clientX}, ${t.clientY})</h2>`)) 

You can test it here :

StreamMap

Regards

Thread Thread
 
leob profile image
leob

Thanks, excellent, yes I get the idea ... I only mentioned RxJS because that's how I ever heard about streams and observables :-)

So can I put it like this: Streams provide a different approach (paradigm, if you will) to working with state in an "immutable" way?

Because that's basically what Redux and Context (and all of React, for that matter) tell us:

Do NOT modify state directly - return a new copy of your state, so treat it as 'immutable' ... streams do just that, but in a different (and probably easier or more powerful) way.

Thread Thread
 
artydev profile image
artydev • Edited

:-)

I am in favor of simplicity, If you are creating a project for your own needs, you really don't need heavy tools.

Use Meiosis with whatever view library you want or nothing eventually.

May I suggest you to use Mithril ?, simple, light and a very kind community :-)

Regards

Thread Thread
 
leob profile image
leob

I've heard about Mithril, but I also heard that Svelte is the greatest thing since sliced bread ... can you compare those two?

Thread Thread
 
artydev profile image
artydev • Edited

Much has been said on Svelte the "javascript compiler".
I can only agree with the speed and tiny apps it produces.
But for my needs I don't see what it brings more than Mithril

Mithril is only Javascript, with Svelte you have to learn a new syntax,
and you can't start without tooling, so integrate it into existing applications is not obvious

For example I needed to extends functionality to an Editor by adding a 'photo' button
with Mithril it was a piece of cake, It would not been that easier with Svelte.

JoditMithril

The more you use Mithril the more you know Javascript, I am not sure this is the case for all the well known frameworks

Thread Thread
 
leob profile image
leob

Right, yes I understand that they're different philosophies, more or less the opposite ...

Mithril is something you can drop into an existing page to enhance it, Svelte is more something you use to build a complete standalone app, it doesn't easily integrate, and it introduces its own tooling and syntax (a bit comparable with Vue with its template syntax, while React takes pride in sticking closer to standard JS, at least that's what it claims).

Totally different beasts really :-)

Thread Thread
 
artydev profile image
artydev • Edited

:-) you can also build complete apps with Mithril.

Among Mithril's users Nike.

Look carefully to this page :-)

Who use Mithril

Regards

Collapse
 
nikhilkumaran profile image
Nikhil Kumaran S

Yeah. Looks similar.