/redux-zero

A lightweight state container based on Redux

Primary LanguageTypeScriptMIT LicenseMIT

redux zero logo

A lightweight state container based on Redux

Read the intro blog post


codacy build npm downloads license dependencies

Table of Contents

Installation

To install the stable version:

npm install --save redux-zero

This assumes that you’re using npm with a module bundler like webpack

How

ES2015+:

import createStore from "redux-zero"
import { Provider, connect } from "redux-zero/react"

TypeScript:

import * as createStore from "redux-zero"
import { Provider, connect } from "redux-zero/react"

CommonJS:

const createStore = require("redux-zero")
const { Provider, connect } = require("redux-zero/react")

UMD:

<!-- the store -->
<script src="https://unpkg.com/redux-zero/dist/redux-zero.min.js"></script>

<!-- for react -->
<script src="https://unpkg.com/redux-zero/react/index.min.js"></script>

<!-- for preact -->
<script src="https://unpkg.com/redux-zero/preact/index.min.js"></script>

<!-- for vue -->
<script src="https://unpkg.com/redux-zero/vue/index.min.js"></script>

<!-- for svelte -->
<script src="https://unpkg.com/redux-zero/svelte/index.min.js"></script>

Example

Let's make an increment/decrement simple application with React:

First, create your store. This is where your application state will live:

/* store.js */
import createStore from "redux-zero";

const initialState = { count: 1 };
const store = createStore(initialState);

export default store;

Then, create your actions. This is where you change the state from your store:

/* actions.js */
const actions = store => ({
  increment: state => ({ count: state.count + 1 }),
  decrement: state => ({ count: state.count - 1 })
});

export default actions;

By the way, because the actions are bound to the store, they are just pure functions :)

Now create your component. With Redux Zero your component can focus 100% on the UI and just call the actions that will automatically update the state:

/* Counter.js */
import React from "react";
import { connect } from "redux-zero/react";

import actions from "./actions";

const mapToProps = ({ count }) => ({ count });

export default connect(mapToProps, actions)(({ count, increment, decrement }) => (
  <div>
    <h1>{count}</h1>
    <div>
      <button onClick={decrement}>decrement</button>
      <button onClick={increment}>increment</button>
    </div>
  </div>
));

Last but not least, plug the whole thing in your index file:

/* index.js */
import React from "react";
import { render } from "react-dom";
import { Provider } from "redux-zero/react";

import store from "./store";

import Counter from "./Counter";

const App = () => (
  <Provider store={store}>
    <Counter />
  </Provider>
);

render(<App />, document.getElementById("root"));

Here's the full version: https://codesandbox.io/s/n5orzr5mxj

More examples

Actions

There are two gotchas with Redux Zero's actions:

  • Passing arguments
  • Combining actions

Passing arguments

Here's how you can pass arguments to actions:

const Component = ({ count, incrementOf }) => (
  <h1 onClick={() => incrementOf(10)}>{count}</h1>
)

const mapToProps = ({ count }) => ({ count })

const actions = store => ({
  incrementOf: (state, value) => ({ count: state.count + value })
})

const ConnectedComponent = connect(mapToProps, actions)(Component)

const App = () => (
  <Provider store={store}>
    <ConnectedComponent />
  </Provider>
)

Combining actions

There's a simple way to combine actions in Redux Zero by using the spread operator. Here's how:

import { connect } from "redux-zero/react";

import Component from "./Component";
import firstActions from "../../actions/firstActions";
import secondActions from "../../actions/secondActions";

export default connect(
  ({ params, moreParams }) => ({ params, moreParams }),
  (...actionParams) => ({
    ...firstActions(...actionParams),
    ...secondActions(...actionParams)
  })
)(Component);

Async

Async actions in Redux Zero are almost as simple as sync ones. Here's an example:

const mapActions = ({ setState }) => ({
  getTodos() {
    setState({ loading: true });

    return client.get("/todos")
      .then(payload => ({ payload, loading: false }))
      .catch(error => ({ error, loading: false }))
  }
});

They're still pure functions. You'll need to invoke setState if you have a loading status. But at the end, it's the same, just return whatever the updated state that you want.

And here's how easy it is to test this:

describe("todo actions", () => {
  let actions, store, listener, unsubscribe;
  beforeEach(() => {
    store = createStore();
    actions = getActions(store);
    listener = jest.fn();
    unsubscribe = store.subscribe(listener);
  });

  it("should fetch todos", () => {
    nock("http://someapi.com/")
      .get("/todos")
      .reply(200, { id: 1, title: "test stuff" });

    return actions.getTodos().then(() => {
      const [LOADING_STATE, SUCCESS_STATE] = listener.mock.calls.map(
        ([call]) => call
      );

      expect(LOADING_STATE.loading).toBe(true);
      expect(SUCCESS_STATE.payload).toEqual({ id: 1, title: "test stuff" });
      expect(SUCCESS_STATE.loading).toBe(false);
    });
  });
});

Middleware

The method signature for the middleware was inspired by redux. The main difference is that action is just a function:

/* store.js */
import createStore from "redux-zero";
import { applyMiddleware } from "redux-zero/middleware"

const logger = (store) => (next) => (action) => {
  console.log('current state', store.getState())
  return next(action);
}

const initialState = { count: 1 };
const middlewares = applyMiddleware(
  logger,
  anotherMiddleware
);

const store = createStore(initialState, middlewares);

export default store;

DevTools

You can setup DevTools middleware in store.js to connect with Redux DevTools and inspect states in the store.

/* store.js */
import createStore from 'redux-zero';
import { applyMiddleware } from 'redux-zero/middleware';
import { connect } from 'redux-zero/devtools';

const initialState = { count: 1 };
const middlewares = connect ? applyMiddleware(connect(initialState)): [];
const store = createStore(initialState, middlewares);

export default store;

Also, these are unofficial tools, maintained by the community:

Inspiration

Redux Zero was based on this gist by @developit

Roadmap

  • Add more examples (including unit tests, SSR, etc)

Docs