0.6.0 • Published 6 years ago

revault v0.6.0

Weekly downloads
2
License
MIT
Repository
github
Last release
6 years ago

Revault

The state management library you've been waiting for

Build Status codecov npm npm JavaScript Style Guide MIT License

Table Of Contents

  1. Why Revault?
  2. Usage
  3. Docs

Why Revault?

Redux is great and without doubt has helped push the web forward by providing a strong mental model around global state. Lately however, a few things have started to frustrate me when using Redux:

  1. Repetition. The boilerplate, just for the most simple task, has become wearisome.
  2. Logic. Business logic is spread out. Some logic lives in the action, some lives in the reducer, and yet some more lives in the component.
  3. Middleware. Apart from logging and handling promises, middleware is more of a hassle than a help.

A few libaries have been released recently that have attempted to solve these issues. Two to note, and mainly where inspiration for Revault was drawn, are Unstated and Statty. Both libraries use a basic component with render props to access global state ❤️ - but each had shortcomings.

Unstated's Container works well to control business logic and encapsulate several pieces of state, all while feeling very familiar to Component local state. Containers, otherwise known as Stores in Revault, live on, only accessed differently on render.

Statty's approach to access is great - by using a select prop to pluck only the pieces of state you want, it's easy to inject derived state as a render prop, while also making it easy to check for referential equality to prevent unecessary renders. Statty was only missing a dedicated logic unit.

Thus, Revault was born - marrying the concepts of Unstated and Statty in what looks to be a happy union. Hope y'all like it! 😎

Usage

Begin by creating your first store, which extends from Store:

import { Store } from 'revault';

export default class TodoStore extends Store {
  state = {
    input: '',
    entries: [],
  };

  updateInput = input => {
    this.setState({ input });
  }

  addTodo = todo => {
    // setState acts like React component's setState,
    // meaning that it runs asynchronously and can also be passed an updater function.
    this.setState({
      entries: [...this.state.entries, todo],
    });
  }
}

Next, wrap your application with the Provider and pass in your stores.

import { render } from 'react-dom';
import { Provider as VaultProvider } from 'revault';
import * as stores from './stores';

/*
  `stores` may look something like the following. The key's will be as
  identifier's when accessing the store during render.
  {
    todos: TodoStore,
    ...etc
  }
*/

const App = () => (
  <VaultProvider stores={stores}>
    <Entry />
  </VaultProvider>
);

render(<App />, window.root);

Finally, drum roll please 🥁, use the Connect component to access our vault on render:

import { Connect } from 'revault';

export default () => (
  <Connect
    select={(stores) => ({
      todos: stores.todos.state.entries,
      input: stores.todos.state.input,
      updateInput: stores.todos.updateInput,
      addTodo: stores.todos.addTodo,
    })}
  >
    {({ todos, input, updateInput, addTodo }) => (
      <>
        <ul>
          {todos.map(todo => (
            <li>{todo}</li>
          ))}
        </ul>

        <form onSubmit={addTodo}>
          <input value={input} onChange={updateInput} />
          <button type="submit" >
            Submit
          </button>
        </form>
      </>
    )}
  </Connect>
)

You can also use the connect HOC if you need to perform more complex logic in component methods:

import React, { Component } from 'react';
import { connect } from 'revault';

@connect((stores) => ({
  todos: stores.todos.state.entries,
  input: stores.todos.state.input,
  updateInput: stores.todos.updateInput,
  addTodo: stores.todos.addTodo,
}))
export default class TodoList extends Component {
  render() {
    const { todos, input, updateInput, addTodo } = this.props;

    return (
      <>
        <ul>
          {todos.map(todo => (
            <li>{todo}</li>
          ))}
        </ul>

        <form onSubmit={addTodo}>
          <input value={input} onChange={updateInput} />
          <button type="submit" >
            Submit
          </button>
        </form>
      </>
    )
  }
}

You've done it! You have your first todo app up and running 3 simple steps.

Docs

<Provider>

Make the vault available to <Connect> via context

props

stores

object | required

A hash of stores. The key will be used as the accessor name when selecting state. The value is your Store constructor.

vault

object

Alternatively, you can pass in a preinstantiated vault. This is helpful during testing.

logger

function(oldState: object, newState: object)

Use the inspector prop during development to log state changes.

revault comes with a default logger inspired by unstated-debug.

import logger from 'revault/logger';

<Provider
  stores={{
    todos: TodoStore,
  }}
  logger={logger}
/>

<Connect>

Connect is a PureComponent that observes pieces of state and re-renders only when those pieces of state update.

props

select

function(stores: object, state: object) | defaults to s => s | returns object

Selects the slice of the state needed by the children components.

lifecycle

object

Access lifecycle methods of <Connect>. Each method has the same signature as select - so they will be passed stores and state. Comes with support for:

  • didMount
  • didUpdate
  • willUnmount

Often, we need to do work in the lifecycle methods but that can be difficult when using functional components. lifecycle makes it easy to kick off async work when mounting or performing cleanup when unmounting.

<Connect
  select={() => ({})}
  lifecycle={{
    didMount(stores) {
      stores.users.fetch(id);
    },
    willUnmount(stores) {
      stores.users.cleanup();
    }
  }}
/>
render

function(state: object) | required

The render fn is passed the observed state returned by select. You can also use a child function.

LICENSE

MIT License © Kyle Alwyn

0.6.0

6 years ago

0.5.1

6 years ago

0.5.0

6 years ago

0.4.3

6 years ago

0.4.2

6 years ago

0.4.1

6 years ago

0.4.0

6 years ago

0.3.0

6 years ago

0.2.6

6 years ago

0.2.5

6 years ago

0.2.4

6 years ago

0.2.3

6 years ago

0.2.2

6 years ago

0.2.1

6 years ago

0.1.1

6 years ago