<h1 align="center">
  <img src="https://github.com/ForsakenHarmony/parket/blob/master/.github/logo.png?raw=true">
</h1>

<br />

<div align="center">
  <a href="https://travis-ci.org/ForsakenHarmony/parket">
    <img src="https://travis-ci.org/ForsakenHarmony/parket.svg?branch=master" alt="travis">
  </a>
  <a href='https://coveralls.io/github/ForsakenHarmony/parket?branch=master'>
    <img src='https://coveralls.io/repos/github/ForsakenHarmony/parket/badge.svg?branch=master' alt='Coverage Status' />
  </a>
  <a href="https://www.npmjs.org/package/parket">
    <img src="https://img.shields.io/npm/v/parket.svg" alt="npm">
  </a>
  <a href="https://greenkeeper.io/">
    <img src="https://badges.greenkeeper.io/ForsakenHarmony/parket.svg" alt="Greenkeeper badge" />
  </a>
  <a href="https://www.npmjs.com/package/parket">
    <img src="https://img.shields.io/npm/dm/parket.svg" alt="version" />
  </a>
  <a href="https://oss.ninja/mit/forsakenharmony">
    <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" />
  </a>
</div>

<br>

- Small (~1.5KB)
- Immutable from the outside, mutable in actions
- Reactive (state emits updates without explicit calls to i.e. `setState`)
- Modular (you can nest models inside each other)

## Why?

I was disappointed with all the current state management solutions.
Then I found mobx-state-tree, which seemed like a godsend to me (ok not really, but I liked the concept), but it was pretty big in terms of file size (mobx alone is big: 16.5kB).
So I thought it's surely possible to make a smaller version of it, that's how this started.
And after 2 failed attempts I finally got something that works well

## Installation

```
$ npm i parket
```

```js
// ES6
import { model } from 'parket';
// CJS
const { model } = require('parket');
```

## Usage

Note: This library uses Proxies and Symbols. Proxies cannot be fully polyfilled so you have to target modern browers which support Proxies.

### Basic example

```js
import { model } from 'parket';
// model returns a "constructor" function
const Person = model('Person', {
  // name is used internally for serialization
  initial: () => ({
    firstname: null,
    lastname: null,
    nested: null,
  }),
  actions: (state) => ({
    setFirstName(first) {
      state.firstname = first; // no set state, no returns to merge, it's reactive™
    },
    setLastName(last) {
      state.lastname = last;
    },
    setNested(nested) {
      state.nested = nested;
    },
  }),
  views: (state) => ({
    fullname: () => `${state.firstname} ${state.lastname}`, // views are computed properties
  }),
});

// merge an object with the initial state
const instance = Person({ firstname: 'Tom' });

// you can subscribe to actions, patches (state updates) and snapshots (full state after actions)
const unsubscribe = instance.onSnapshot(console.log);

// you can unsubscribe by calling the function returned by the listener
// unsubscribe();

instance.setLastName('Clancy');

// views turn into cached getters
console.log(instance.fullname); // 'Tom Clancy'

// nested models also bubble up events to the parent
instance.setNested(Person());

instance.nested.setFirstName('wow');

// you can get a snapshot of the state at any time
// { firstname: 'Tom', lastname: 'Clancy',  nested: { firstname: 'wow', lastname: null, nested: null } }
console.log(instance.getSnapshot());
```

### Async example

```js
const Async = model('Async', {
  initial: () => ({
    loading: false,
    result: null,
  }),
  actions: (self) => ({
    async doSomethingAsync() {
      // actions can be async, parket doesn't care
      self.loading = true;
      self.result = await somethingAsync(); // be aware that you should handle errors
      self.loading = false;
    },
  }),
});
```

### preact / react

```js
import { Component } from 'preact';
import { observe, connect, Provider } from 'parket/preact'; // or 'parket/react'

// observe keeps the component updated to models in the prop
@observe
class Observed extends Component {
  render({ person }) {
    // if you're using react, props don't get passed to render so you have to use `const {person} = this.props;`
    return (
      <div>
        <h1>{person.fullname}</h1>
      </div>
    );
  }
}

// connect inserts the store/instance into props
@connect
class Person extends Component {
  render({ store }) {
    // if you're using react, props don't get passed to render so you have to use `const {store} = this.props;`
    return (
      <div>
        <h1>{store.fullname}</h1>
      </div>
    );
  }
}

// Provider adds an instance to the context
const root = () => (
  <Provider store={instance}>
    <div id="app">
      <Person />
      <Observed person={instance} />
    </div>
  </Provider>
);
```

### preact / react hooks

```js
function Observed({ person }) {
  useObserved(person);

  return (
    <div>
      <h1>{person.fullname}</h1>
    </div>
  );
}

function Person() {
  const store = useStore();

  return (
    <div>
      <h1>{store.fullname}</h1>
    </div>
  );
}

// Provider adds an instance to the context
const root = () => (
  <Provider store={instance}>
    <div id="app">
      <Person />
      <Observed person={instance} />
    </div>
  </Provider>
);
```

## Credits

- [Mobx State Tree for Inspiration](https://github.com/mobxjs/mobx-state-tree)
- [Zouhir for the awesome logo](https://twitter.com/_zouhir)

## License

[MIT © hrmny.sh](https://oss.ninja/mit/forsakenharmony)