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

redux-schema

v4.8.0

Published

Automatic actions, reducers and validation for Redux. Use State like mutable objects without violating any of the redux principles.

Downloads

14

Readme

Redux-Schema

build status Coveralls npm version npm downloads

Introduction

Redux Schema is a system to use Redux without needing to write any action creators or reducers. If you don't know what Redux is all about, you should spend some time looking at it. There's a whole community for you to connect with.

Redux is based on 3 principles:

  • Single source of truth
  • State is read-only
  • Changes are made with pure functions

Also, the state is best kept serializable so it can be packed up and shipped easily.

The above principles create applications that are easy to manage as they grow from tiny tests into large complex applications.

Why Schema?

Redux is a very small library. It's designed to help without getting in the way. It only covers a very small area, namely managing the state. Even there it doesn't touch the state. It leaves this to the reducers, which copy-and-modify the state.

The code to copy-and-modify the state is fairly simple in each case, and using rest spread and such, it's even fairly clean. The matching action creators are also tiny and quick to write.

The trouble comes when you require many actions with matching reducers. The reducers usually live in a separate file. Nonetheless, most often each action creator is paired with a single reducer case. Moreover, the action creators are extremely similar from one to the next and writing them quickly feels like boilerplate coding. The reducers, due to their pure-functional nature, aren't always easily readable. The intent of simply setting a property is easily lost in code like return { ...state, myProp: action.value }. Also, this code is embedded in a case statement that can grow to unwieldly proportions.

And when you have these reducers and action creators, you have to make sure they are being tested. Each has to be matched with a test or 2 to make sure it does its job.

Less obvious when you start coding this way is that you lose out on something we're very much used to when we write JavaScript, and that is Object Oriented Programming. By turning every mutation into an action and sending this to a central processing plant (the reducer), the code to act on our data is no longer attached to the data. user.friend(otherUser) becomes dispatch(friendUser(requester, invitee)) and the actual code that does the work is found elsewhere and can't reference this.

Redux-Schema is designed to overcome these issues. It allows you to use Redux without needing to write any reducers, actionTypes, actionCreators or dispatch calls.

Example

To whet your appetite, check out the todo example in examples/todos. Compare it to the redux version of the same example. You'll see that it contains a lot less code.

In particular:

  • No action types
  • No action creators
  • No reducers
  • No selectors (mapStateToProps)
  • No action selecting / binding (mapDispatchToProps)

What does it look like?

A picture is worth 1000 words. Unfortunately, I'm no artist. So here's some code:

import schemaStore, { model, optional, Nil, bare, reference, collections, union } from 'redux-schema';
import { createStore } from 'redux';


let userModel = schema('User', {
  first: optional(String),
  last: optional(String),

  address: union(schema.Nil, {
    street: String,
    town: String
  }, {
    POBox: String,
    town: String
  }),

  constructor(foo, bar) {
    if (foo === bar) {
      console.log('The foo is the bar!')
    } else {
      console.log('The foo and the bar are not the same.')
    }
  },

  get full() {
    return this.first + ' ' + this.last;
  },

  set full(full) {
    let split = full.split(' ');
    this.first = split.shift();
    this.last = split.join(' ');
  },

  getGreeting() {
    if (this.full === 'Foo Bar') {
      return 'Baz Quux!';
    } else {
      return 'Hello ' + this.first + ' from ' + this.address.town;
    }
  },

  friend: optional(schema.reference('user')),

  makeFoo() {
    this.full = 'Foo Bar';
  }
});

let root = collections([userModel]);

let store = schemaStore(root, { debug: true })(redux.createStore)();

let { User } = store.models;

let user = new User('foo', 'bar');
/*
  generates:

  dispatch({
    type: 'USER_CONSTRUCTOR',
    path: [ 'user', 'fc6e4b60004c11e6963a4dd9', 'constructor' ],
    args: [ 'foo', 'bar' ]
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        first: undefined,
        last: undefined,
        address: { type: '1:union', value: null },
        friend: undefined,
        id: '9b66b7d0005111e68f23a7ab'
      }
    }
  }
*/

console.log(user.full); //"undefined undefined"
/* This doesn't generate any action */

user.full = 'First Last';
/*
  generates:

  dispatch({
    type: 'USER_SET_FULL',
    path: [ 'user', 'fc6e4b60004c11e6963a4dd9', 'full' ],
    value: 'First Last'
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: { type: '1:union', value: null },
        id: '9b66b7d0005111e68f23a7ab',
        friend: undefined,
        first: 'First',
        last: 'Last'
      }
    }
  }
*/

console.log(user.full); //First Last

user.makeFoo();
/*
  generates:
  dispatch({
    type: 'USER_MAKE_FOO',
    path: [ 'user', '9b66b7d0005111e68f23a7ab', 'makeFoo' ],
    args: []
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: { type: '1:union', value: null },
        id: '9b66b7d0005111e68f23a7ab',
        friend: undefined,
        first: 'Foo',
        last: 'Bar'
      }
    }
  }
*/

console.log(user.getGreeting());
/*
  Because it's wrapped in schema.bare, it doesn't generate any action.
  If it did set any properties, it would result in the same actions as if
  when those properties were set from outside a method.
*/

user.address = { street: '123 west somewhere', town: 'Wiggletown' };
/*

  generates:
  dispatch({
    type: 'SET_USER_ADDRESS',
    prop: true,
    path: [ 'user', '9b66b7d0005111e68f23a7ab', 'address' ],
    value: {
      type: '1:object',
      value: { street: '123 west somewhere', town: 'Wiggletown' }
    }
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: {
          type: '1:object',
          value: { street: '123 west somewhere', town: 'Wiggletown' }
        },
        id: '9b66b7d0005111e68f23a7ab',
        friend: undefined,
        first: 'Foo',
        last: 'Bar'
      }
    }
  }

  Note that the storage of the union of 2 different objects results in the
  store having extra information about the data type. This doesn't interfere
  with the usage of this data. The store is simply the backend representation.
*/

let ref1 = user.address;
user.address = { POBox : '101', town: '12' };
/*

  generates:
  {
    type: 'SET_USER_ADDRESS',
    prop: true,
    path: [ 'user', '9b66b7d0005111e68f23a7ab', 'address' ],
    value: {
      type: '2:object', value: { POBox: '101', town: '12' }
    }
  }

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: {
          type: '2:object',
          value: { POBox: '101', town: '12' }
        },
        id: '9b66b7d0005111e68f23a7ab',
        friend: undefined,
        first: 'Foo',
        last: 'Bar'
      }
    }
  }

  The type of the object is automatically inferred.
*/

user.address = { street: '123 west somewhere', town: 'Wiggletown' };
/*

  generates:
  dispatch({
    type: 'SET_USER_ADDRESS',
    prop: true,
    path: [ 'user', '9b66b7d0005111e68f23a7ab', 'address' ],
    value: {
      type: '1:object',
      value: { street: '123 west somewhere', town: 'Wiggletown' }
    }
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: {
          type: '1:object',
          value: { street: '123 west somewhere', town: 'Wiggletown' }
        },
        id: '9b66b7d0005111e68f23a7ab',
        friend: undefined,
        first: 'Foo',
        last: 'Bar'
      }
    }
  }
*/
let ref2 = user.address;

console.log(ref1 === ref2, ref1.street, ref2.street)); //true, '123 west somewhere', '123 west somewhere';

/*

  A cache ensures that references to the same object of the same type are
  actually the same instance. Even if they wouldn't be the same instance,
  however, the property values would be sourced from the same store. Thus,
  the only way to know they are different is to compare the instances with
  strict equal.

*/

user.friend = user;

/*

  generates:
  dispatch({
    type: 'SET_USER_FRIEND',
    prop: true,
    path: [ 'user', '9b66b7d0005111e68f23a7ab', 'friend' ],
    value: '9b66b7d0005111e68f23a7ab'
  });

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: {
          type: '1:object',
          value: { street: '123 west somewhere', town: 'Wiggletown' }
        },
        id: '9b66b7d0005111e68f23a7ab',
        first: 'Foo',
        last: 'Bar',
        friend: '9b66b7d0005111e68f23a7ab'
      }
    }
  }
*/

console.log(store.rootInstance.user.keys); //[ '9b66b7d0005111e68f23a7ab' ]

new User();
new User();
/*

  new state:
  {
    user: {
      '9b66b7d0005111e68f23a7ab': {
        address: {
          type: '1:object',
          value: { street: '123 west somewhere', town: 'Wiggletown' }
        },
        id: '6a879770005511e68e3269d9',
        first: 'Foo',
        last: 'Bar',
        friend: '6a879770005511e68e3269d9'
      },
      '6a8b6800005511e68e3269d9': {
        first: undefined,
        last: undefined,
        address: { type: '1:union', value: null },
        friend: undefined,
        id: '6a8b6800005511e68e3269d9'
      },
      '6a8b8f10005511e68e3269d9': {
        first: undefined,
        last: undefined,
        address: { type: '1:union', value: null },
        friend: undefined,
        id: '6a8b8f10005511e68e3269d9'
      }
    }
  }

*/
console.log(store.rootInstance.user.keys);
//[ '9b66b7d0005111e68f23a7ab', '6a8b6800005511e68e3269d9', '6a8b8f10005511e68e3269d9' ]

Work in progress

There's still a lot of work to be done:

  • More Tests
  • Better documentation
  • Allow to set defaults
  • Code cleanup
  • Add method parameter type descriptions and automatic serialization and deserialization of arguments
  • Automatic Promise resolution for properties