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

@sunny-g/cycle-redux-driver

v0.1.17

Published

(un)official Redux Action and State driver for Cycle.js

Downloads

33

Readme

cycle-redux-driver

(un)official Redux Action and State driver for Cycle.js

installation

npm install --save @sunny-g/cycle-redux-driver

usage

Basic usage with the Cycle.js counter example:

import { combineReducers } from 'redux';
import makeReduxDriver from '@sunny-g/cycle-redux-driver';
// your standard Redux action type constants, action creators and reducers
import countReducer, {
  initialCount,
  INCREMENT, DECREMENT,
  increment, decrement,
} from './redux.js';

// your typical Redux root reducer
const reducer = combineReducers({
  count: countReducer,
});

function main(sources) {
  // `sources.REDUX.state` is the stream of the Redux store
  // `select` (eventually) takes in a lens or selector
  const props$ = sources.REDUX.state
    .select();

  const vdom$ = props$
    .map(({ count }) =>
      div([
        button('.decrement', 'Decrement'),
        button('.increment', 'Increment'),
        p('Counter: ' + count)
      ])
    );

  // the sink is a stream of an object of action streams
  // the keys should be the action types, values are the actual action streams
  const action$ = xs.of({
    [DECREMENT]: sources.DOM
      .select('.decrement')
      .events('click')
      .map(_ => decrement(1)),
    [INCREMENT]: sources.DOM
      .select('.increment')
      .events('click')
      .map(_ => increment(1)),
  });

  return {
    DOM: vdom$,
    REDUX: action$,
  };
}

run(main, {
  DOM: makeDOMDriver('#main-container'),
  REDUX: makeReduxDriver(
    reducer,
    { count: initialCount },
    // specify which action types the driver should funnel into Redux store
    [ INCREMENT, DECREMENT ],
    [],
  ),
});

Alternatively, you can choose to ignore the state source and Redux store altogether and manage the state yourself (with onionify or something similar).

In this next example, we'll map our actions to reducers, and fold them into local state manually:

// same imports as before

function main(sources) {
  const incrementReducer$ = sources.REDUX.action
    .select(INCREMENT)
    .map(({ payload }) =>
      ({ count }) =>
        ({ count: count + payload })
    );

  const decrementReducer$ = sources.REDUX.action
    .select(DECREMENT)
    .map(({ payload }) =>
      ({ count }) =>
        ({ count: count - payload })
    );

  // if using `onionify`, `stanga` or something similar,
  // return `reducer$` and map your provided state source stream to `vdom$`
  const reducer$ = incrementReducer$
    .merge(decrementReducer$);

  const props$ = reducer$
    .fold((state, reducer) =>
      reducer(state),
    { count: initialCount });

  // same vdom$ as before
  const vdom$ = props$
    .map(({ count }) =>
      div([
        button('.decrement', 'Decrement'),
        button('.increment', 'Increment'),
        p('Counter: ' + count)
      ])
    );

  // same action$ sink as before
  const action$ = xs.of({
    [DECREMENT]: sources.DOM
      .select('.decrement')
      .events('click')
      .map(_ => decrement(1)),
    [INCREMENT]: sources.DOM
      .select('.increment')
      .events('click')
      .map(_ => increment(1)),
  });

  return {
    DOM: vdom$,
    REDUX: action$,
  };
}

run(main, {
  DOM: makeDOMDriver('#main-container'),
  REDUX: makeReduxDriver(),	// not technically using Redux anymore, so pass no arguments to drivers
});

api

makeReduxDriver

parameters:
  • reducer?: Reducer<any>: The same Redux reducer you'd pass into createStore
  • initialState?: any: The same Redux initial state you'd pass into createStore
  • actionsForStore?: string[]: List of action types that could result in a store's state change
    • every action stream is lazy by default (unless selected within your application)
    • therefore, in order to preserve as much laziness as possible, we use this array to inform the driver to (eagerly) subscribe to and funnel into the store only the action streams that contribute to Redux state
  • middlewares?: Middleware[]: The same Redux middlewares you'd pass into createStore

NOTE: All parameters are optional in case you only want to use the action source.

Example:

run(main, {
  // ... other drivers
  REDUX: makeReduxDriver(
    reducer,
    { count: initialCount },
    [ INCREMENT, DECREMENT ],
    [],
  ),
});

redux.action source

redux.action.select(type?: string): ActionStream | ActionSink

parameters:
  • type?: string: A stream that emits action objects of the specified by type
returns:
  • Stream<FSA> | Stream<{ [type: string]: Stream<FSA> }>: A stream of FSA-compliant action objects
    • NOTE: the meta property of the action object is an object with the key '$$CYCLE_ACTION_SCOPE' - this key is required for Cycle.js isolation
    • if type was omitted, the stream returned is the raw ActionSink that was passed into the driver so that you can create your own custom action streams

Example:

const incrementReducer$ = sources.REDUX.action
  .select(INCREMENT)
  .map(({ type, payload, error, meta }) =>
    ({ count }) => ({ count: count + payload })
  );

redux.state source

redux.state.select(): StateStream<any>

returns:
  • MemoryStream<any>: A stream that emits the Redux store's current state every time the state has changed

Example:

const state$ = sources.REDUX.state
  .select();

redux sink: ActionSink

should return:
  • Stream<{ [type: string]: Stream<FSA> }>: A stream of objects, where each key is a specific action type and each value is the stream that emits action objects of that type.

Example:

// INCREMENT, DECREMENT are action type constants
// increment, decrement are action creators

return {
  // ... other sinks...
  REDUX: of({
    [DECREMENT] : sources.DOM
      .select('.decrement')
      .events('click')
      .map(_ => decrement(1)),
    [INCREMENT] : sources.DOM
      .select('.increment')
      .events('click')
      .map(_ => increment(1)),
  }),
};

helpers

createReducer(initialState: any, reducers: { [type: string]: Reducer })

Combines a set of related reducers into a single reducer

parameters:
  • initialState: any: The initial state of a Redux "state machine"
  • reducers: { [type: string]: Reducer }: An object whose keys are the action types and the values are the reducers that respond to those actions and whose signature is (state: any, action: FSA) => any
returns:
  • a combined reducer of the same aformentioned signature

makeActionCreator(type: string)

Creates a shorthand function for creating action objects

parameters:
  • type: string: The action type of the action object
returns:
  • actionCreator: (payload: any, error: bool = false, meta: object = {}) => FSA: A function that creates FSA-compliant action objects with the properties type, payload, error, and meta

contributing

todo

  • ensure typescript typings are correct and comprehensive and exported correctly
  • evaluate whether or not optimizations can be made to reduce the number fo emissions of actions of a particular type
  • enhance use without store:
    • no store in dev (subscribes to all action$s)
    • no store in prod (doesn't use redux, only circulates actions)
  • ensure build tooling with tsc and webpack is correct
  • refactor implementation to not require redux if not using the state source
  • add testing mock action and state sources
  • explain contribution process
  • add more tests :)
  • explain why I wrote this

license

ISC