npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cycle/collection

v0.7.0

Published

Manage a collection of item in your dataflow component

Downloads

23

Readme

cycle-collections

Collection is no longer actively maintained. We recommend you check out @cycle/state

An easier way to do collections in Cycle

Components can be hard to manage in Cycle.js. They can be especially painful when you're working with lists of components.

Collection is a helper function that makes managing your lists of components a cinch.

Installation

$ npm install @cycle/collection --save

How does it work?

import Collection from '@cycle/collection';

Let's say we have a TodoListItem component, and we want to make a TodoList.

function TodoListItem (sources) {
  // ...

  return sinks;
}

You can make a collection stream by calling Collection() and passing it a component.

const todoListItems$ = Collection(TodoListItem);

It's common in Cycle that you want to pass your sources to your children. You can pass a sources object as the second argument. Each item in the collection will be passed these sources.

const todoListItems$ = Collection(TodoListItem, sources);

To actually populate the collection, you pass an add$ stream. Its emitted values may be sources objects, which will be merged with the sources object you passed when you created the collection$. This is useful for passing props$. For Collection versions 0.6.0+ (based on Cycle Unified), any stream type supported by Cycle.js should work automatically. See the old README if you are older versions of Cycle.js (Cycle.js Diversity).

const todoListItems$ = Collection(TodoListItem, sources, xs.of(additionalSources));

add$ can emit an array, if multiple items should be added at once.

const todoListItems$ = Collection(TodoListItem, sources, xs.of([firstSources, secondSources]));

Collection() returns a stream with arrays of items as values. Those arrays are cloned from internal ones, so changes will not impact the state of the collection$.

Collections are immutable. This is because in Cycle.js values that change are represented as streams.

If we put it all together in our TodoList it looks like this:

function TodoList (sources) {
  const addTodo$ = sources.DOM
    .select('.add-todo')
    .events('click')
    .mapTo(null); // to prevent adding click events as sources

  const todoListItems$ = Collection(TodoListItem, sources, addTodo$);

  const sinks = {
    DOM: xs.of(
      div('.todo-list', [
        button('.add-todo', 'Add todo')
      ])
    )
  }

  return sinks;
}

Wait, how do we get the todoListItems to show up in the DOM?

Collection.pluck to the rescue!

const todoListItemVtrees$ = Collection.pluck(todoListItems$, item => item.DOM);

Collection.pluck takes a collection stream and a selector function and returns a stream of arrays of the latest value for each item. Selector function takes the sinks object and returns a stream. So for the DOM property each item in the stream is an array of vtrees. It handles the map/combine/flatten for you and also ensures that any vtree streams have unique keys on their values. This improves performance quite a bit and helps snabbdom tell the difference between each item.

We can now map over todoListItemVtrees$ to display our todoListItems.

function TodoList (sources) {
  const addTodo$ = sources.DOM
    .select('.add-todo')
    .events('click')
    .mapTo(null); // to prevent adding click events as sources

  const todoListItems$ = Collection(TodoListItem, sources, addTodo$);

  const todoListItemVtrees$ = Collection.pluck(todoListItems$, item => item.DOM);

  const sinks = {
    DOM: todoListItemVtrees$.map(vtrees =>
      div('.todo-list', [
        button('.add-todo', 'Add todo'),

        div('.items', vtrees)
      ])
    )
  }

  return sinks;
}

But wait, there's more!

There is another common and hard to solve problem with lists in Cycle.js.

Say our TodoListItem has a 'remove' button. What happens when you click it?

A TodoListItem can't really remove itself. The state of the parent element needs to change.

All that a TodoListItem can do is return a remove$ stream as part of it's sinks, along with DOM.

Normally, to solve this problem you would need to create a circular reference between the sinks of the items in your collections and the stream of reducers you're folding over. This is achieved using imitate in xs or Subject in rx. This can be tricky code to write and read, and often adds quite a bit of boilerplate to your component.

When you create a Collection you can optionally pass a removeSelector function that returns a stream which will trigger item's removal.

const todoListItems$ = Collection(TodoListItem, sources, add$, item => item.remove$);

All together now!

function TodoList (sources) {
  const addTodo$ = sources.DOM
    .select('.add-todo')
    .events('click')
    .mapTo(null); // to prevent adding click events as sources

  const todoListItems$ = Collection(TodoListItem, sources, addTodo$, item => item.remove$);

  const todoListItemVtrees$ = Collection.pluck(todoListItems$, item => item.DOM);

  const sinks = {
    DOM: todoListItemVtrees$.map(vtrees =>
      div('.todo-list', [
        button('.add-todo', 'Add todo'),

        div('.items', vtrees)
      ])
    )
  }

  return sinks;
}

And how do we process fetched data?

It's a quite common use case when a collection is built from fetched data. Usually it comes in a form of items' state snapshot. Collection.gather takes a stream of those snapshots and turns into a stream of collections. It takes Collection and sources arguments, just as Collection does, plus the snapshots stream itemState$, an optional idAttribute argument, which defaults to 'id', and an optional transformKey function for converting source keys.

const tasks$ = Collection.gather(Task, sources, fetchedTasks$, 'uid', key => `${key}$`) // converts 'props' in snapshots to 'props$' in sources

It uses a set of rules:

  • items are keyed by idAttribute.
  • items that weren't present in the previous snapshot are added to collection.
  • each added item tracks it's own state, turning the sequence of each field's values into a source.
  • item is removed from collection if it's no more present in a snapshot.

So what if our components issue HTTP requests?

There are kinds of sinks that rather represent actions than states. HTTP sink is a good example. If we want to get a stream of all HTTP requests issued by collection's items, Collection.merge will provide us one. It works basically the same as Collection.pluck, but merges the sinks instead of combining them into array.

const tasksRequest$ = Collection.merge(tasks$, item => item.HTTP);

Importing Collection directly is the same as calling makeCollection().