1.3.0 • Published 1 year ago

@zambezi/caballo-vivo v1.3.0

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

= Caballo Vivo

quote, CJC 1177. los cementerios no deben estrenarse enterrando un hombre muerto sino un caballo vivo

Thin, opinionated layer for pure RxJS applications to connect with React-Router (or any router using history). It has the advantage of scaling well, allowing more concise and expressive code and a better separation between business logic and view layer.

Here's the equivalent implementation of the Redux Reddit advanced tutorial with caballo-vivo, in less than 100 lines of code (https://codesandbox.io/s/caballo-vivo-reddit-w88kg[online sandbox]):

source,javascript

// type localStorage.setItem('cv-log', true) in the console to see the logs! import React from "react" import { render } from "react-dom" import { OrderedMap, Map } from "immutable" import { partialRight } from "ramda" import { Subject, merge, concat, of, from } from "rxjs" import { map, catchError, switchMap } from "rxjs/operators" import { Router, Switch, Route, Link } from "react-router-dom" import { createLocation$, createNavigateTo$, createStore$, history, flog, stow } from "@zambezi/caballo-vivo" import ClipLoader from "react-spinners/ClipLoader" import "./index.css"

const getPosts$ = new Subject()

const pathToIntent = OrderedMap([ "/:subreddit", ({ subreddit }) => getPosts$.next({ subreddit }), "/", () => getPosts$.next({ subreddit: "all" }) ])

const location$ = createLocation$(pathToIntent).pipe( // <1> map(location => state => state.set("location", location)) )

const redditJourney$ = getPosts$.pipe( // <2> switchMap(({ subreddit }) => concat( of(state => state.set("loading", true)), // <3> from(fetch(https://www.reddit.com/r/${subreddit}.json)).pipe( switchMap(res => from(res.json()).pipe( map(json => json.data.children.map(child => child.data)) ) ), stow("subreddit") // <4> ), createNavigateTo$(subreddit), // <5> of(state => state.set("loading", false)) ) ), catchError(() => of(state => state.set("error", "This subreddit is not available")) ) )

merge(location$, redditJourney$) .pipe(createStore$(Map()), flog("Render state"), map(toView)) // <6> .subscribe(partialRight(render, document.getElementById("root")))

function toView(state) { // <7> if (state.has("error")) return {state.get("error")} if (state.get("loading")) return (

  <div className="loader">
    <ClipLoader />
  </div>
)

return (

  <section className="links">
    {["all", "reactjs", "rxjs", "javascript", "node"].map(l => (
      <Link key={l} to={l}>{`r/${l}`}</Link>
    ))}
  </section>
  <hr />
  <Switch>
    <Route path="/:subreddit">
      <ul>
        {state.get("subreddit").map(post => (
          <li key={post.id}>
            <a href={post.url} target="_blank" rel="noopener noreferrer">
              {post.title}
            </a>
          </li>
        ))}
      </ul>
    </Route>
  </Switch>
</Router>

)

}

<1> Passing a map to createLocation$ will block the hisory when the route changes and the new url matches one of the keys. <2> We listen to the getPosts$ intent, triggered by the routes above, to kickstart a complex series of asynchronous events (set/unset loader flag, download subreddit). <3> When we emit reducer functions, they get executed by the store (see below). All that a reducer function does is get the current state snapshot and return a new snapshot. <4> https://github.com/zambezi/caballo-vivo/blob/master/src/stow.js[stow] is just an operator which emits a reducer function to set the value received on a key or path in the store. This pattern is so common that it got its own operator. <5> createNavigateTo$ unblocks the history and changes the address in the URL bar. At this point, all the routes matching the new address can render. <6> createStore$ creates a store, which is an rxjs operator that has an initial value, takes reducers function in and spits state snapshots out. <7> The view function is just a function which takes state snapshots in and generates JSX. Views and state / business logic are decoupled and interchangeable.

For a more advanced example application, please check: https://github.com/gabrielmontagne/fa-doodle

1.3.0

1 year ago

1.2.1

3 years ago

1.2.1-0

3 years ago

1.2.0

4 years ago

1.1.3

4 years ago

1.1.2

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.2-0

5 years ago

1.0.1

5 years ago

1.0.1-1

5 years ago

1.0.1-0

5 years ago

1.0.0

5 years ago

1.0.0-1

5 years ago

1.0.0-0

5 years ago

0.5.0

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.0

5 years ago

0.1.0

5 years ago

0.0.1

5 years ago