A lightweight state container based on Redux
Read the intro blog post
To install the stable version:
npm install --save redux-zero
This assumes that you’re using npm with a module bundler like webpack
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>
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
There are two gotchas with Redux Zero's actions:
- Passing arguments
- Combining actions
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>
)
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 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);
});
});
});
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;
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:
Redux Zero was based on this gist by @developit
- Add more examples (including unit tests, SSR, etc)