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

bdux

v18.0.5

Published

A Flux implementation

Downloads

124

Readme

Bdux

A Flux architecture implementation out of enjoyment of Bacon.js, Redux and React.

Build Status Coverage Status Codacy Badge

Want to achieve

  • Reactive all the way from action to React component.
  • Redux style time travel through middleware and reducer.
  • Only activate reducer when there is a subscriber.
  • Utilise stateless functional React component.
  • Utilise React context provider and consumer.

Installation

To install as an npm package:

npm install --save bdux

Action

Action creator returns:

  • A single action object.
  • A Bacon stream of action objects.
  • A falsy value to create no action.

Example of action creators:

import Bacon from 'baconjs'
import ActionTypes from './action-types'

export const add = () => ({
  type: ActionTypes.ADD
})

export const complete = () => (
  Bacon.once({
    type: ActionTypes.COMPLETE
  })
)

export const remove = (index) => {
  if (index >= 0) {
    return {
      type: ActionTypes.REMOVE
    }
  }
}

Store

Store is created using createStore(name, getReducer, otherStores = {}).

  • name specifies a unique store name, which can be:
    • A string.
    • A function props => ({ name }).
  • getReducer returns a reducer as Pluggable which is an object contains the input and output of a stream.
  • otherStores is an object of dependent stores.

Reducer stream:

  • Receives an input object { action, state, dispatch, bindToDispatch, ...dependencies }.
  • Should always output the next state according purely on the input object.
  • Should NOT have intermediate state. e.g. scan or skipDuplicates.
  • Should NOT have side effect. e.g. flatMap or throttle.

Have intermediate states and side effects in action creators instead. So time travelling can be achieved, and there is a single point to monitor all actions which could cause state changes. Store can dispatch actions which will be queued to cause state changes in other stores.

Example of a store:

import R from 'ramda'
import Bacon from 'baconjs'
import ActionTypes from '../actions/action-types'
import StoreNames from '../stores/store-names'
import { createStore } from 'bdux'

const isAction = R.pathEq(
  ['action', 'type']
)

const whenCancel = R.when(
  isAction(ActionTypes.CANCEL),
  R.assocPath(['state', 'confirm'], false)
)

const whenConfirm = R.when(
  isAction(ActionTypes.CONFIRM),
  R.assocPath(['state', 'confirm'], true)
)

const getOutputStream = (reducerStream) => (
  reducerStream
    .map(whenCancel)
    .map(whenConfirm)
    .map(R.prop('state'))
)

export const getReducer = () => {
  const reducerStream = new Bacon.Bus()

  return {
    input: reducerStream,
    output: getOutputStream(reducerStream)
  }
}

export default createStore(
  StoreNames.DIALOG, getReducer
)

Dealing with a collection of data is a common and repetitive theme for store. Creating a separate store for the items in the collection can be a great tool for the scenario. Simply construct the store names dynamically from props for individual items.

Example of constrcuting store names:

const getConfig = props => ({
  name: `${StoreNames.PRODUCT}_${props.productId}`,

  // mark the store instance as removable
  // to be removed on component unmount.
  isRemovable: true,
  // default value will be null if not configured.
  defaultValue: {
    items: [],
  },
})

export default createStore(
  getConfig, getReducer
)

Component

Component can subscribe to dependent stores using hooks useBdux(props, stores = {}, callbacks = [], skipDuplicates) or createUseBdux(stores = {}, callbacks = [], skipDuplicates)(props).

  • stores is an object of dependent stores.
  • callbacks is any array of functions to be triggered after subscribing to stores.
  • skipDuplicates is a function to map store properties. The default behaviour is map(property => property.skipDuplicates()).

The hooks return an object of:

  • state is an object of the current values of stores.
  • dispatch is a function to dispatch the return value of an action creator to stores.
  • bindToDispatch binds a single action creator or an object of action creators to dispatch actions to stores.

Example of a component:

import R from 'ramda'
import React, { useMemo, useCallback } from 'react'
import * as CountDownAction from '../actions/countdown-action'
import CountDownStore from '../stores/countdown-store'
import { createUseBdux } from 'bdux'

const useBdux = createUseBdux({
  countdown: CountDownStore
}, [
  // start counting down.
  CountDownAction.countdown
])

const CountDown = (props) => {
  const { state, dispatch, bindToDispatch } = useBdux(props)

  const handleClick = useMemo(() => (
    bindToDispatch(CountDownAction.click)
  ), [bindToDispatch])

  const handleDoubleClick = useCallback(() => {
    dispatch(CountDownAction.doubleClick())
  }, [dispatch])

  return R.is(Number, state.countdown) && (
    <button
      onClick={ handleClick }
      onDoubleClick={ handleDoubleClick }
    >
      { state.countdown }
    </button>
  )
}

export default React.memo(CountDown)

Wrap the entire app in a bdux context provider optionally to avoid of using global dispatcher and stores, which is also useful for server side rendering to isolate requests.

import React from 'react'
import { createRoot } from 'react-dom/client';
import { BduxContext, createDispatcher } from 'bdux'
import App from './components/app'

const bduxContext = {
  dispatcher: createDispatcher(),
  stores: new WeakMap()
}

const renderApp = () => (
  <BduxContext.Provider value={bduxContext}>
    <App />
  </BduxContext.Provider>
)

createRoot(document.getElementById('app'));
  .render(renderApp())

Middleware

Middleware exports getPreReduce, getPostReduce and useHook optionally.

  • getPreReduce returns a Pluggable stream to be applied before all reducers.
  • getPostReduce returns a Pluggable stream to be applied after reducers.
  • useHook is triggered in all components which include useBdux.

Example of a middleware:

import Bacon from 'baconjs'

const logPreReduce = ({ action }) => {
  console.log('before reducer')
}

const logPostReduce = ({ nextState }) => {
  console.log('after reducer')
}

export const getPreReduce = (/*{ name, dispatch, bindToDispatch }*/) => {
  const preStream = new Bacon.Bus()

  return {
    input: preStream,
    output: preStream
      .doAction(logPreReduce)
  }
}

export const getPostReduce = () => {
  const postStream = new Bacon.Bus()

  return {
    input: postStream,
    output: postStream
      .doAction(logPostReduce)
  }
}

Apply middleware

Middleware should be configured before importing any store.

Example of applying middlewares:

import * as Logger from 'bdux-logger'
import * as Timetravel from 'bdux-timetravel'
import { applyMiddleware } from 'bdux'

applyMiddleware(
  Timetravel,
  Logger
)

Examples

License

The ISC License