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

storext

v0.3.0

Published

Manage your immutable app state with ease using storext

Downloads

3

Readme

Storext

An immutable state management library for your React app that utilize the unidirectional data flow which fits on React ecosystem. It produces a predictable state container which helps managing the state of your React app. It is powered by Immutable.js.

Storext gives you ease in handling your states, and on top of that, it is also easy to test.

NPM JavaScript Style Guide Build Status

Influence

Storext used the ideas on Flux Architecture, Redux, and Vuex. It uses new React context api for handling context and Immutable.js for efficient re-rendering.

Note

Storext will only work in React >=16.3

Installation

npm install --save storext

Guide 📖

Table of Contents

Getting Started

Start by looking through the guides and examples on Github.

How storext works

Each state of the features of your app is managed by a specific store. The state is transformed into immutable state. The only way to update the state of a store is to commit an updater function which returns a state change object. Updater can be commit directly by React components or commit by Thunks. Once the state of a store changes, all consumed components observing on a specificic data gets re-render. Storext treats collections as values. It achieve this behavior using Immutable.js. To check the architectural pattern uses by storext, click on this link.

Storext Parts

  • Store - A store is what holds the data of an application. The only way to mutate the data on the store is to respond on the stateChange object.
  • Info - An object which holds the payload of information. It contains the type of updater or thunk and data which is sent to store. This is the only data use in store.
  • Updater - It specifies how to update the current state of a store. It receives a data coming from the info object. It returns a stateChange object. Updaters are invoke using commit function.
  • Thunk - A function which can contain arbitrary asynchronous operations and conditional logic. Inside thunk, it can commit an updaters. It receives a data coming from the info object. The idea behind thunk is influenced by redux-thunk. Thunks are invoked using dispatch function.
  • React View - The state is displayed on the React view.

Quick Start

import { createStore, consume } from 'storext'

// create initial Counter state. This object is transformed into immutable.js collections. Uses fromJS in transforming.
const initialState = {
  count: 0
}

// create Counter store. This object has a fields of state, updaters, and thunks. Thunks is optional.
const counterStore = {
  state: initialState,
  /**
 * This is an updater function. It specifies how to update the current state of a store.
 * Function signature is (payload, localState) => stateChange. This is the only way to change the state of 
 * a store. The state is immutable collection. Update it using immutable.js API. In this way, we can 
 * avoid mutating the state directly.
 */
  updaters: {
    increment (payload, state) {
      // updates the count data of counter store.
      return state.update('count', (value) => value + payload.count)
    },
    decrement (payload, state) {
      return state.update('count', (value) => value - payload.count)
    }
  }
}

// declare the stores use by the React app.
const stores = {
  counter: counterStore
}

/**
* Creating main store based on the given stores. Main store creates
* immutable state tree based on the state of every stores. It returns a main store object that has Provider component. 
* Provider component exposes the immutable state 
*/
const { Provider } = createStore(stores)

/**
 * Storext used the Container-Presentational design pattern which conceptualize by React. 
 * It complements the architecture of storext. For more info 
 * - https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0.
 */
 
/**
 * Consuming the state and handlers to our Counter component. We must define a 
 * mapStateToProps and mapHandlersToProps. It returns a Container component.
 * It is concerned with how things work. Provides the data and behavior to
 * presentational or other container components.
 */

// Mapping the state to props. Function signature is (globalState, ownProps) => mapState
const mapStateToProps = (state) => ({
  count: state.counter.get('count') // access the counter store state. Get the count value on Map counter.
})

/**
 * Mapping handlers to props. Function signature is (commit, dispatch, globalState) => handlers. 
 */ 
const mapHandlersToProps = (commit) => ({
  handleDecrement () {
    // when commiting an updater, we need to pass an info object specifying the updater type and payload.
    commit({
      type: 'decrement',
      payload: {count: 10}
    })
  },
  handleIncrement () {
    commit({
      type: 'increment',
      payload: {count: 10}
    })
  }
})

const ContainerCounter = consume(mapStateToProps, mapHandlersToProps)(Counter)

/**
 * Presentational Component is concerned with how things look.
 */
const Counter = ({count, handlers: {handleDecrement, handleIncrement}}) => (
  <div>
    <button onClick={handleDecrement}>Decrement</button>
    <span>{count}</span>
    <button onClick={handleIncrement}>Increment</button>
  </div>
)

/**
 * Mount the components.
 */
const App = () => (
  <Provider>
    <ContainerCounter />
  </Provider>
)

Handling Asynchronous and Composing Updater

When developing an app, we often encounter evaluating some asyc computations. Then we need to update the state of store based on the response of async. Storext provides handy solution on how to handle async computations. Here comes Thunks. A thunk can contain arbitrary asynchronous operations and it can commit updater functions.

/**
* It is common pattern to declare constant updater types. This allow us to group and 
* see entire updaters and thunks which are used in the app.
*/
const PENDING = 'PENDING'
const SUCCESS_GET_USERS = 'SUCCESS_GET_USERS'
const FETCH_USERS = 'FETCH_USERS'

const pending = ({pending}, state) => state.setIn(['process', 'pending'], pending)
const success = ({success}) => (state) => state.setIn(['process', 'success'], success
const getUsers = ({users}) => (state) => state.set('users', users)

/**
 * Because our updaters are pure function, we can compose an updater based on result
 * of other updaters. We call it Updater Composition. We can use compose helper function 
 * from 3rd-party library like ramda or lodash but its optional. Learn composition pattern
 * - https://medium.com/davao-js/make-fp-fun-what-the-heck-is-composition-f8707674a177
 *
 * Try to avoid calling multiple updaters in a row. This can cause issue in 
 * logger states utility. Check the logger utility section for more info.
 */
const successGetUsers = ({pending, success, users}, state) => {
  return compose(
    getUsers({users}),
    success({success})
  )(state)
}

const userStore = {
  state: {
    users: {},
    process: {
      pending: false,
      success: false
    }
  },
  updater: {
    // We can use the ES2015 computed property name in declaring methods
    [PENDING]: pending,
    [SUCCESS_GET_USERS]: successGetUsers
  },
  thunks: {
    // The thunk returns an inner function which has signature (commit, globalState, thunkExtraArg) => any. 
    [FETCH_USERS] () {
      return function inner (commit) {
        // commit PENDING updater. Pass an info object.
        commit({type: PENDING, payload: {pending: true}})
        // Whatever value return by innerFn, it will be the return value of dispatch function. 
        return ajaxFetchUsers()
          .then((users) => {
            // commit SUCCESS_GET_USERS updater. Pass an info object.
            commit({
              type: SUCCESS_GET_USERS,
              payload: {
                success: true,
                users
              }
            })
          })
      }
    }
  }
}

// Declare handlers
const mapHandlersToProps = (commit, dispatch) => ({
  handleFetch () {
    // when dispatching a thunk, we need to pass an info object like in committing updater which specifies the thunk type and payload. 
    // Payload is optional.
    dispatch({type: FETCH_USERS})
      // once the inner function of thunk returns a Promise, we can chain a 
      // Promise here as long it returns a value.
      .then(() => console.log('DONE!'))
  }
})

Using selectors

Rule of thumb in managing state through Storext is to store minimal state as possible to every stores. Through selectors, it can compute the derived state from the store which is possible to avoid state complexity.

// create users count selector
const usersCountSelector = (state) => state.get('users').count()

// pass the state of users store.
const mapStateToProps = ({users}) => ({
  count: usersCountSelector(users)
})

const Container = consume(mapStateToProps, {})(Presentational)

To create more efficient selectors, take a look of Reselect library.

Utilities

Storext includes some utilities which can help your productivity and reduce some boilerplates. Some utilities included are infoCreator and infoCreators. These utilities are used for creating info object which is a payload of information.

Sometimes creating info object which pass to updater/thunk is little bit cumbersome. Helpers infoCreator and infoCreators help to reduce the complexity and make it reusable.

Using infoCreator:

import { infoCreator } from 'storext'

/**
 *
 * infoCreator helper is an higher order function which returns infoCreator function. infoCreator creates an infoObject.
 * Function signature - (infoType) => infoCreator . It also modifies the output of 'toString' method of the return function. 
 * It will output the given infoType and transform to camelCase. For are instance, given infoType is 
 * ADD_TODO -> the toString value of created infoCreator is 'addTodo'. Therefore, we can use addTodo as expression in
 * computed property name.
 */
 
const addTodo = infoCreator('ADD_TODO')

const updaters = {
  // Instead hardcoding the name of updater function, we can use the reference addTodo as computed property name.
  [addTodo]: (payload, state) => ...code
}

const mapHandlersToProps = (commit) => ({
  handleAddTodo () {
    // create info object with payload
    const info = addTodo({id: 1, text: 'Travel'}) // {type: 'ADD_TODO', payload: {id: 1, text: 'Travel'}}
    // Pass the info object to commit an updater.
    commit(info)
  }
})

Using infoCreators:

import { infoCreators } from 'storext'

/**
 * infoCreators returns an object which map types to info creators. The keys of the object is camel-case from the elements included on the given array types. Function signature is (types) => Object.
 */

const { addTodo, removeTodo } = infoCreators(['ADD_TODO', 'REMOVE_TODO']) // {addTodo: function receiver () {}, removeTodo: function receiver () {}}

const updaters = {
  [addTodo]: (payload, state) => ...code,
  [removeTodo]: (payload, state) => ...code
}

const mapHandlersToProps = (commit) => ({
  handleAddTodo () {
    const info = addTodo({id: 1, text: 'Travel'}) // {type: 'ADD_TODO', payload: {id: 1, text: 'Travel'}}
    commit(info)
  },
  handleRemoveTodo () {
    const info = removeTodo({id: 1}) // {type: 'REMOVE_TODO', payload: {id: 1}}
    commit(info)
  }
})

Changelog

This project adheres to Semantic Versioning. Every release, along with the migration instructions, is documented on the Github Releases page.

License

MIT