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

stato

v1.0.1

Published

Functional Reactive State Management

Downloads

4

Readme

stato Dependencies

Super simple functional reactive state management library powered by Bacon.js 🔥

Installation

npm install --save stato

Then just either import the main stato function, the Store class (controlling bus you need to instantiate), or both (depending what you need on each file).

import stato, {Store} from 'stato';

Motivation and Proposed Architecture

Just a lil bit of context first re: functional reactive programming. The most fundamental concept of Functional Reactive Programming (FRP) is the event stream. Streams are like (immutable) arrays of events: they can be mapped, filtered, merged and combined. The difference between arrays and event streams is that values (events) of the event stream occur asynchronously. Every time an event occurs, it gets propagated through the stream and finally gets consumed by the subscriber.

We have Flux and other implementations such as Redux and MobX to handle our app state, and in fact they do a great job abstracting our views from the business logic and keeping our data flow unidirectional. However, Reactive programming is what React was made for. So, what if we delegate the app state handling to FRP libraries like Bacon.js or RxJS? Well, that actually makes a lot of sense:

  1. Actions happen eventually and they propagate through event streams.
  2. The combination of these event streams result in the app's state.
  3. After an event has propagated through the system, the new state is consumed by the subscriber and rendered by the root level React component.

This makes the data flow super simple:

Application Architecture

The fundamental idea behind this approach is that every user-triggered action gets pushed to the appropriate event stream, which is then merged in to the application state stream which is our single source of truth. Events take place at different points in time, and they cause the application state to change. Finally the updated state triggers a re-render of the root component, and React's virtual DOM takes care of the rest :tada: This results in dead simple, dumb React views, mostly powered by stateless functional components in favour of stateful class components (wherever possible).

TL;DR: Usage Example

Have a look at this example.

Quick Start Guide

1) Define your action types

export const SHOW_SPINNER = 'SPINNER/SHOW';

I usually define all actions within a single file for convenience.

2) Create your reducers

Reducers are pure functions that derive the next application state for a particular action, based on the current state and the payload the action provides. The first parameter reducers take is always the current state for the app, whereas the rest of the arguments are whatever data your reducer needs and you pass on to them.

Reducers and action types have a 1:1 relationship. You need to name your reducers after the action type they are bound to ⚠️ -- this is the only style convention this library has.

export default {
  [SHOW_SPINNER]: (state) => {
    return Object.assign({}, state, { loading: true });
  },

  [HIDE_SPINNER]: (state) => {
    return Object.assign({}, state, { loading: false });
  }
}

Of course reducers don't need to be inline functions, you can define them elsewhere and then bind them together in the format stato needs them to be, something in the lines of this chunk of code... But this is totally up to you and depends on your preferred code style.

export default {
  [SHOW_SPINNER]: showSpinnerReducer,
  [HIDE_SPINNER]: hideSpinnerReducer
}

3) Define your initial state

const initialState = {
  loading: false
};

4) Instantiate your store

const store = new Store();

5) Initialise your application state

stato(initialState, store, reducers, (props) => {
  ReactDOM.render(
    <App {...props} />,
    document.getElementById('app')
  );
});

About Side Effects

Side effects allow your application to interact with the outside world, i.e.: fetching data from an API, getting/setting data from/to localStorage/sessionStorage, talking to a database, etc.

Unlike reducers, effects are not pure functions.

Naturally effects may (and most usually do) trigger actions to update the application state once they are done making asynchronous operations.

For instance, consider this effect called getUserDetails that fetches a list of users from an API. Provided the Ajax request completes successfully, the effect will trigger the RECEIVE_USER_DETAILS action which simply updates the application state with those user details. This allows for a separation of concerns between hitting an API and updating the app state.

export function getUserDetails() {
  store.push(SHOW_SPINNER); // triggers an action
  
  const ajaxCall = fetch('//api.github.com/users/fknussel')
    .then((response) => response.json());

  const userDetailsStream = Bacon
    .fromPromise(ajaxCall)
    .onValue((user) => {
      store.push(GET_USER_DETAILS, user); // triggers an action
      store.push(HIDE_SPINNER); // triggers an action
    });
}

Using stato with other view libraries

stato is actually view-layer agnostic, so it can easily be used with any other UI library such as Preact or Vue.js.

Development Tasks

| Command | Description | |---------|-------------| | npm install | Fetch dependencies and build binaries for any of the modules | | npm run clean | Remove lib directory | | npm run build | Build lib/stato.js file | | npm test | Run test suite |

Complementary Readings, Inspiration and Credits

I've first used a somewhat similar architecture while at Fox Sports Australia and it made perfect sense. This was probably before or at the same time Redux and MobX became popular.

Matti Lankinen proposes the same idea on his article on Medium. I've made tweaks and enhancements to this library after some of his comments and ideas.