simred
A simple redux-like application state manager
- Does not use actions objects, but function calls
- Supports async actions out of the box
- Does not require
redux-thunk
, similar functionality is provided - Does not require
redux-actions
- Does not require
reduce-reducers
to handle relations between parts of state - Third parties can listen to state changes through
subscribe()
- Supports middlewares through
addMiddleware()
- Has type definitions for Typescript™
TODO
- Allow for "scoped" reducers
- Code Splitting
Table of Content
- Installation
- Getting Started
- Core Concepts
- Basics
- Complete Example: To-do List
- Usage with React
- License
Installation
$ npm install --save simred
Getting Started
The first example is a simple counter. Same as other state management libraries such as Redux, the state in contained in a single readonly object. This only way to change it is to call "actions". To determine how actions change the state, you write pure reducers.
/** * This is a reducer, * an object with only functions with signatures of: * (state, actions, ...args) => state * It discribes operation to transform the state * * The shape of the state is up to you. */const counter = state + 1 state - 1 // Creates a Simred store to hold your dataconst store = Simred // You can subscribe to any changes to the state to make your// app reactive to changes, or store the state to localStoragestore // The only way to change the state is to call an action// you can access actions through the getActions() getterconst actions = storeactions actionscounter // state == { counter: 1 }actionscounter // state == { counter: 2 }actionscounter // state == { counter: 1 }
Core concepts
Simred works in a similar way as Redux, and respects the same three principles:
Single source of truth The global state is stored in a single object, in a single store.
State is read-only The only way to change the state is though actions.
Changes are made with pure functions Actions in the reducers should not mutate the state, but return a new object.
Basics
Actions
Actions are the only way to change the state. They are functions of a reducer (see next section) with the following signature:
// synchronous actionsPartial<State> // asynchronous actionsPartial<State>
The state
argument is a copy of the part of state the action relates to.
The actions
argument is a read-only object containing all other actions in the store.
Actions return an update of state
.
Actions can:
- be asynchronous, return Promises and call other actions.
- return a partial state.
Actions must:
- return an new object, not mutate the state.
Important
Example declaration:
const increment = (state, actions) => (n) => state + n
Example call:
actions.counter.increment(3)
Reducers
A reducer is a collection of actions to manage a part of state. They allow to organize your code better, splitting logic across separate reusable modules.
An initial state can be provided to define default values for the part of state the reducer manages. If no initial state is provided, the reducer manages the whole state.
Simred exposes a dedicated decorator function to create reducers:
withInitialStateinitialState?: any:Reducer;
Examples: A reducer managing only a part of state:
const actions = ...state item const initialState = initialStateactions
A reducer managing the whole state:
const actions = list: ...statelist item counter: statelistlength + 1 actions
Combining reducers
const rootReducer = a: reducerA b: reducerB // storeState == { a: [], b: { value: false } }
Actions from reducerA
will receive storeState.a
as state
.
Actions from reducerB
will receive storeState.b
as state
.
Actions in reducerB
can only interact with storeState.a
by calling actions declared in reducerA
through the actions
argument.
Example:
// reducerA.js // ... ...state item
// reducerB.js // ... value: false { // calls "add" from reducerA actionsa return value }
Store
Now that we know about actions and reducers, let's bring them all together: that is what the store does.
- The store holds the application state;
- Allows access through
getState()
; - Allows access to actions (and therefore change the state) through
getActions()
; - Register listeners through
subscribe()
; - Registers middlewares thtough
addMiddleware()
;
The store can be created easily once reducers have been combined:
const store = Simred
An initial state can be specified as second argument. This is useful to hydrate the state with user data matching the Simred state from external sources (server, localStorage, ...)
const stateFromLocalStorage = JSON const store = Simred
Calling actions
// Log the initial stateconsole // Every time the state changes, log it// Note that subscribe() returns a function for unregistering the listenerstore const actions = storeactions // Dispatch some actionsactionstodos)actionstodos)actionstodos)actionstodosactionstodosactionsvisiblityFilter
Middlewares
Middlewares can be used to intercept actions as they are propagated through the store. They can be used for a variety of things, such as logging, storing the state, etc.
A middleware consist in a function with signature:
void
Where:
store
is the store, with.actions
,.getState()
, etcnext
is the next middleware in the stack, middleware are executed in the same order they added to the storeaction
is the name of the part of state and of the function (e.g.:"todos.add"
)args
is an array of arguments the action received (e.g.:["Learn about actions"]
)
Middleware can be added in one of two ways:
- using
Simred.createStore(reducers, initialState, middlewares)
, where the third argument is an array of middlewares - using
store.addMiddleware(middleware)
, on an existing store
Here is an example of a logging middleware:
// The middleware itselfconst logger = { console // Calls the next middleware on the stack}) // add it at store creation timeconst store = Simred // OR after the factstore
Complete Example: To-do List
Let's design a store for a simple to-do list manager.
Todos belongs a single list troughout the application. The have a text and a completed status.
We want the following functionalities:
- add a todo to the list of todos;
- toggle the completed status of a given todo;
- show either: all todos, completed todos, or remaining todos.
Designing the state
We'll use Typescript™ notation for easy reading.
We identified two types of entities:
- todos
- visibility filer
We'll need two parts of state, and therefore, two reducers:
// One Todo item // Visibility Filter constants
The Todo Reducer
// todoReducer.js /* Initial state for todos: an empty list */const INITIAL_STATE = const todoReducer = /* appends a new todo to the list, completed defaults to false */ ...state text completed: false /* toggles the completed flag of the todo at index */ { return state }
The Visibility Filter Reducer
// filters.js /* Constants for the visibility filter */const VisibilityFilters = SHOW_COMPLETED: "SHOW_COMPLETED" SHOW_REMAINING: "SHOW_REMAINING" SHOW_ALL: "SHOW_ALL"
// visibilityFilterReducer.js // The application will show all todos by defaultconst INITIAL_STATE = VisibilityFilterSHOW_ALL const visibilityFilterReducer = /* changes the value of the visibilty filter */ filter
The Root Reducer
// reducers.js /** * the todoReducer will manage the part of state 'todos' * the visibilityFilterReducer will manage the part of state 'visibilityFilter' */const rootReducer = todos: todoReducer visibilityFilter: visibilityFilterReducer
The Store
// store.js const store = Simred // log all changes to the statestore
A test program
// index.js const actions = storeactions actionstodos)actionstodos)actionstodos)actionstodosactionstodosactionsvisiblityFilter
Console:
{ todos: [ { text: "Learn about actions", completed: false } ], visibilityFilter: "SHOW_ALL" }{ todos: [ { text: "Learn about actions", completed: false }, { text: "Learn about reducers", completed: false } ], visibilityFilter: "SHOW_ALL" }{ todos: [ { text: "Learn about actions", completed: false }, { text: "Learn about reducers", completed: false }, { text: "Learn about store", completed: false } ], visibilityFilter: "SHOW_ALL" }{ todos: [ { text: "Learn about actions", completed: true }, { text: "Learn about reducers", completed: false }, { text: "Learn about store", completed: false } ], visibilityFilter: "SHOW_ALL" }{ todos: [ { text: "Learn about actions", completed: true }, { text: "Learn about reducers", completed: true }, { text: "Learn about store", completed: false } ], visibilityFilter: "SHOW_ALL" }{ todos: [ { text: "Learn about actions", completed: true }, { text: "Learn about reducers", completed: false }, { text: "Learn about store", completed: false } ], visibilityFilter: "SHOW_COMPLETED" }
Usage with React
See the react-simred project to learn how to use Simred with React
License
MIT © Gaël PHILIPPE