Coder Social home page Coder Social logo

colshacol / cratebox Goto Github PK

View Code? Open in Web Editor NEW

This project forked from alexvcasillas/cratebox

0.0 0.0 0.0 274 KB

State Management Library for no particular UI library

Home Page: https://www.npmjs.com/package/cratebox

License: MIT License

JavaScript 58.56% TypeScript 41.44%

cratebox's Introduction

Cratebox Logo

CircleCI PRs Welcome first-timers-only

Cratebox is an Opinionated State Management library for no particular UI library.

Contents

Motivation

I started working on Cratebox to prove myself I was able to build a powerful state management library following the example of top notch developers like Michel Weststrate, Dan Abramov, Jamie and many more. I wanted something robust and typed like MobX State Tree but also wanted something clear and easy to use, and that's why I built Cratebox, to be an intermediate point between MobX State Tree and Redux, robust and with a declarative and simple API that does the job always.

Why Cratebox?

This is a question that often comes to my mind. Why you should use Cratebox?. The answer could be: because you might like it. Or because you find an opinionated library interesting so you don't have to overwhelm yourself with decisions. Maybe it's because you like this simple API that it has. Using Cratebox is up to you. I've been working with Redux and MobX State Tree for a very long time and I believe Cratebox is right in between of this to impressive libraries and will get things done if you give it a try, for sure!

Installation

NPM

npm install cratebox

Yarn

yarn add cratebox

Getting Started

To get started with Cratebox first of all you need to import the dependency into your project plus the typings dependency for your models.

import { cratebox, types } from 'cratebox`
// Instantiate CrateBox
const myCratebox = cratebox();

From that point on, you'll have access to the full API of Cratebox.

Describing a Store

myCratebox.describeStore({
  identifier: 'user',
  model: {
    name: types.string,
    lastName: types.string,
    avatar: types.string,
    age: types.number,
    birthDate: types.date,
    position: types.string,
    github: types.string,
  }
});

Let's take a look at the example above and let's get though all of the details.

First of all we have the describeStore function. This function is the one in charge to tell Cratebox that we would like to describe a new store were we will put our state.

The describeStore function takes on parameter that is an object will the following structure:

{
  identifier: string,
  model: model object
}

The identifier property represents the name of the store you're describing, therefore, this will be the name you'll be using latter on to deal with this particular store within Cratebox.

The model property is a model object that represents all of the properties that you're identified store will contain. This properties could be any of the properties that we will describe later on. This properties are typed by our own typing system and will let you build rock-solid-typed models.

As we can see at the describeStore example above, we're defining a model that contains the following properties:

name: types.string,
lastName: types.string,
avatar: types.string,
age: types.number,
birthDate: types.date,
position: types.string,
github: types.string,

Many of the properties described above are of type string, meaning that no mater what you store there, if it's not a string, it will complain about it and, therefore, it wont let you store it at the given property.

There's also a type number and a type date there, meaning that, the property age will only be able to store numeric data and the birthDate property will only be able to store instances of the Date type.

Ok, we've described our store so far at this point. Let's proceed with more stuff about Cratebox.

Dispatching changes

To dispatch a change into a store, we will call the dispatch function from the Cratebox API.

myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Alex',
    lastName: 'Casillas',
    age: 28,
    birthDate: new Date('1990-03-23'),
    avatar: 'https://avatars3.githubusercontent.com/u/9496960?s=460&v=4',
    position: 'Frontend Engineer',
    github: 'https://github.com/alexvcasillas'
  }
})

Let's take a minute to process the code above. Got it? Ok, let's go for it.

The dispatch function takes on single argument that is a dispatch object. This dispatch object contains the following properties:

{
  identifier: string,
  model: model object
}

See some resemblance from something we described before? That's it! It has the same structure that our described store! That makes things even more easy to deal with.

The identifier property will tell Cratebox which of the described stores is the one that's getting a change. In the example above, user store is the one that's getting a change dispatched.

We can dispatch as many changes to a store as we want. You have to consider that you dispatch changes to a model, meaning that, calling the dispatch event with the following data:

myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Antonio',
    lastName: 'Cobos',
    position: 'Backend Engineer',
  }
})

Won't generate a new state with just:

{
  name: 'Antonio',
  lastName: 'Cobos',
  position: 'Backend Engineer',
}

But instead will be:

{
  name: 'Antonio',
  lastName: 'Cobos',
  avatar: 'https://avatars3.githubusercontent.com/u/9496960?s=460&v=4',
  age: 28,
  birthDate: '1990-03-23',
  position: 'Backend Engineer Engineer',
  github: 'https://github.com/alexvcasillas'
}

As you can see, it will generate changes based on the previous state, making sure that state changes to a single value won't break the state of your app.

You also have to take note that properties that are not described at the describeStore() function, won't affect your state if you try to dispatch properties not defined previously. For example:

myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Antonio',
    lastName: 'Cobos',
    position: 'Backend Engineer',
    mightyLevel: 'Over 9000!!'
  }
})

This dispatch call will generate a new state with the described properties updated but the mightyLevel property will be discarded.

Retrieving State

You can retrieve the current state of a specific store making use of our simple exposed API method for this: getState(identifier: string)

This method will retrieve the current state of a store by the given identifier. Let's look at an example:

// Describe the store
myCratebox.describeStore({
  identifier: 'user',
  model: {
    name: types.string,
    lastName: types.string,
  }
});

// Dispatch a new change at the user store
myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Alex',
    lastName: 'Casillas',
  }
})

// Call the Get State method
console.log( myCratebox.getState('user') );

The code above will give you the following output in your console:

{
  name: 'Alex',
  lastName: 'Casillas',
}

Subscriptions

Subscriptions will let you listen to changes to a given store. This way you'll be able to update your UI (for example) based on state changes you made to your store.

The way of subscribing to changes within a particular store is:

myCratebox.subscribe('user', model => {
  // Handle your changes the way you want here :)
});

Let's dive into the subscribe method now.

This method takes two arguments, the identifier of the store you want to track state changes to and a callback function that will give you the current state model after a dispatch action make changes to a store.

Let's see the following case as example:

// Describe the store
myCratebox.describeStore({
  identifier: 'user',
  model: {
    name: types.string,
    lastName: types.string,
  }
});

// Create a subscriber for the user store
myCratebox.subscribe('user', model => {
  console.log(model);
})

// Dispatch a new change at the user store
myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Antonio',
    lastName: 'Cobos',
  }
})

At the right moment you dispatch the changes to the user store. The subscription hook will be called and you'll get the following output logged at the console:

{
  name: 'Antonio',
  lastName: 'Cobos',
}

As you can see, subscriptions are pretty simple to use and you don't need to be subscribing to all of your stores' changes, you can subscribe to specific a store changes right whenever you need them.

Time Traveling

Time traveling is supported out of the box for you. We expose a simple API that will let you handle time traveling in a simple way.

Let's say you want to travel backgrounds in your store, then you simply:

myCratebox.travelBackwards('user');

Or let's say you want to travel forwards after you just traveled backwards, simply:

myCratebox.travelForwards('user');

Let's dive a little into the Time Traveling API. It's simple, we expose to methods: travelBackwards and travelForwards.

Both of the methods require one single argument: the store identifier of the store you want to make time travel with.

Let's take a look at this with a little example:

// Describe the store
myCratebox.describeStore({
  identifier: 'user',
  model: {
    name: types.string,
    lastName: types.string,
  }
});

// Create a subscriber for the user store
myCratebox.subscribe('user', model => {
  console.log('Store Changes: ', model);
})

// Dispatch a new change at the user store
myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Alex',
    lastName: 'Casillas',
  }
})

// Dispatch another change at the user store
myCratebox.dispatch({
  identifier: 'user',
  model: {
    name: 'Antonio',
    lastName: 'Cobos',
  }
})

// Call the Travel Backwards method
myCratebox.travelBackwards('user');
// Call the Travel Forwards method
myCratebox.travelForwards('user');

When executing the example above you'll have the following output:

// First Dispatch
Store Changes: { name: 'Alex', lastName: 'Casillas' }
// Second Dispatch
Store Changes: { name: 'Antonio', lastName: 'Cobos' }
// Travel Backwards
Store Changes: { name: 'Alex', lastName: 'Casillas' }
// Travel Forwards
Store Changes: { name: 'Antonio', lastName: 'Cobos' }

If you have noticed, the same subscription you've created for listening to changes when dispatch is called, will also work for Time Traveling out of the box.

Types

Cratebox is a typed state management library and therefore, comes bundled with basic types plus some advanced types.

All of the types are to be imported from the types namespace.

Basic Types

The basic types of Cratebox are the following and self explanatory

  • types.string
  • types.number
  • types.boolean
  • types.null
  • types.undefined
  • types.date

Advanced Types

The advanced types of Cratebox are currently a work in progress but will contain some of the following types for your use:

types.array(type: base)

The array type recieves a basic type as a single parameter and will make sure that all of the values stored at the array are solely that type.

For example:

myCratebox.describeStore({
  identifier: 'user',
  model: {
    name: types.string,
    lastName: types.string,
    notes: types.array(types.string)
  }
});

If you try to include any type different from the base string type it will complain and throw an error.

types.enum(enumeration: string[])

The enumeration type recieves an array of literal strings as a single parameter and will make sure that the value that is trying to be store is one of the described in the enumeration.

For example

myCratebox.describeStore({
  identifier: 'notes',
  model: {
    title: types.string,
    description: types.string
    status: types.enum(['DRAFT', 'PUBLISHED', 'HIDDEN'])
  }
});

If you try to include anything that is not one of the described string literals it will complain and throw an error.

types.literal(literal: string)

The literal type recieves a string literal to check against.

For example

myCratebox.describeStore({
  identifier: 'notes',
  model: {
    title: types.string,
    description: types.string
    status: types.enum(['DRAFT', 'PUBLISHED', 'HIDDEN']),
    fixed: types.literal('IMMUTABLE')
  }
});

If you try to set the property fixed to another value that's not IMMUTABLE, in this example, it will complain and throw an error.

types.frozen(frozen: object)

The frozen type receives an object with any properties that you want.

myCratebox.describeStore({
  identifier: 'notes',
  model: {
    title: types.string,
    description: types.string
    status: types.enum(['DRAFT', 'PUBLISHED', 'HIDDEN']),
    fixed: types.literal('IMMUTABLE'),
    custom: types.frozen
  }
});

myCratebox.dispatch({
  identifier: 'notes',
  model: {
    custom: {
      hasReferences: true,
      shareButtons: true,
      allowComments: false
    }
  }
})

Implementations

Currently Cratebox has a React implementation.

React

To make use of Cratebox with React, go to the official Cratebox React bindings.

Roadmap

We have a roadmap for new implementations and this are our intentions:

  • Specific State Time Traveling via travelTo(index: number) exposed API method.
  • Advanced Types
    • Optional Type
  • Lifecycle hooks
    • Before Create via beforeCreate model property.
    • After Create via afterCreate model property.

Thanks!

Michel Weststrate's incredible work on MobX and MobX-State-Tree that has been a great inspiration since their early stages.

Dan Abramov's work on Redux that also has been a great inspiration while trying to mix it with MobX-State-Tree

And also lots of thanks to you for reading this lines and took some time to read though the docs and maybe gave a try to this project.

cratebox's People

Contributors

alexvcasillas avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.