- BDN-POCKET
- Deprecated
- Why bdn-pocket ?
- Definitions
- Project size
- Principles
- Concepts
- Usage
- PropTypes
- Signal
- Messenger
- Create
- [Combine with redux
reducer
(makeReducer
)](#combine-with-redux-reducer-makereducer) - Call message (from saga)
- Path reducer (
makePathReducer
)
- Selector & SliceSelector
BDN-POCKET
easily manage state management and state selection with redux and redux-saga
Deprecated
see: https://www.npmjs.com/package/@b-flower/bdn-pocket
Why bdn-pocket ?
bdn-pocket is a set of tools that helps to manage state and allow link between redux and redux-saga more easily.
It brings some new concepts like Signal
, Message
and Messenger
that helps to separate ActionCreator
in different categories with separation of responsabilities.
It allows Selector
with arguments
and a clean memoization (inspired by reselect and using it)
It enforces readibility and runtime validation with integration of propTypes
on Action
and Selector
.
bdn-pocket has been built by b-eden development team. This project is an extract of differents concepts and development already existing in b-eden project and gathered now in this projet with some enhancements.
bdn-pocket uses stampit which brings composition and configuration with ease.
b-eden team plans to replace existing b-eden code with this library.
You can use this library with small project.
Definitions
Action
= actionCreator
=> it generates redux action
s
Project size
This library is not intended to be used for small projects.
Action
is powerfull and flexible tools that helps to create action
s.
Selector
extends reselect with some new features and easy composition.
This library has been built for large projects using redux and redux-saga.
Read Principles for explanation.
Principles
At b-eden team we use redux with redux-saga for more than 2 years and that lead to some principles.
- No side effect in a container (and component of course)
- A container should not dispatch an action that change the state
- All side effects should be done in redux-saga
Those principles help b-eden dev team to build a robust, maintenable, readable with comprehensive architecture app.
With separation of concern of Action
between Signal
and Message
Concepts
Action
Action
is the same concept as redux one.
But in b-eden, Action
are never used in favor of 2 new concepts (Signal
, Messenger
).
In bdn-pocket Action
is an action
creator.
Action
generatesaction
sSignal
generatessignal
s
Signal
A Signal
is purely an Action
, it creates an action
.
console // => true
A signal
must follow these principles:
- a (dispatched)
signal
will never be used to change the reduxstate
- a
container
will always call aSignal
(not aMessage
)
Message
A message
is an action
called from a Messenger
.
It is associated to a reducer
.
In bdn-pocket message
is just a definition.
Object Message
does not exists as it is an Action
in a Messenger
.
Messenger
A Messenger
is a tool that links a message
defintion (Action
) with a reducer
(state
).
A message
is an action
that will be associated with a reducer
and will produce an new state
.
A message
must follows this principle:
- never use a
message
in a reduxcontainer
Usage
PropTypes
Signal
and Selector
are composed of PropTypes
(thanks to stampit).
Thus you can enforce props (as React propTypes) you receive and ensure you send good ones.
And it offers readibility and some documentation for the same price.
Available types
number
string
object
func
array
mixed
const number string object func array mixed = Types const sig = Signal // => not throw // => not throw // => throw an error // => throw an error // => throw an error
You can use short notation to define prop types with required string.
const string = Types const sig = Signal // same asconst sig = Signal
Signal
A Signal
is an action creator that creates a signal
.
In the concept of bdn-pocket
a dispatched signal
should not result as a redux state
change.
It's goal is to be watched by saga
.
A redux container
must dispatch a signal
.
Create
// in /user/signal.js const triggerLoadUser = Signal console// => { type: 'my-app/TRIGGER_LOAD_USER', payload: { userId: 'me' }}
Change prefix
// in /user/signal.js const triggerLoadUser = Signalprefix'my-plugin' console// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }}
You can use your own Signal
definition inside a plugin.
// in /lib/my_signal.js prefix'my-plugin' // in /user/signal.jsconst triggerLoadUser = PluginSignal console// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }}
PropTypes
You can enforce prop types of signal
to ensure userId
is present and has good type.
// in /user/signal.jsconst string = Types const triggerLoadUser = Signal console// => { type: 'my-plugin/TRIGGER_LOAD_USER', payload: { userId: 'me' }} // call without userId throw an error // => throw an error
Dispatch
// in /module/user/signal.js const triggerLoadUser = Signal // in /module/user/container null { return { MyComp
Watch (in saga)
// in /module/user/signal.js const triggerLoadUser = Signal // in /module/user/sagas { whiletrue const payload = } { // do some side effect here}
Messenger
A Messenger
is a list of message
s associated with redux reducer
s.
Helper functions help you to combine a messenger
in the global redux reducer
.
Create
/// in /module/user/messages.js const string = Typesconst state = users: 'user1': id: 'a' name: 'name' email: 'email' const user = Messenger // <- DO NOT FORGET TO CREATE YOUR MESSENGER INSTANCE
redux reducer
(makeReducer
)
Combine with /// in /module/user/messages.js // ... same code as before user // <- now you can combine this reducer with global redux reducer
If you define more than one messenger
in a messenger file, you can use redux combineReducer
helper to export a default reducer from your file
/// in /module/user/messages.js const user = Messenger const account = Messenger user: account: // <- now you can combine this reducer with global redux reducer
Call message (from saga)
A message
has to be dispatch in order to call associated reducer.
To create a message
you have to use the message create accessible with key
on messenger
// in /module/user/sagas { whiletrue const payload = } { // do some side effect here ... // here we assume we receive a user data from our server const userData = id: 'me' name: 'Arnaud' email: 'amelon@b-flower.com' // add is the key of Message in messenger // <-- create message -> dispatch (put) -> call reducer -> new state} // other exemple { // do some server stuff ... // now delete user in state // del is the key of Message creator in messenger }
makePathReducer
)
Path reducer (Sometimes (often) you want to use payload property as key of a substate.
In our previous exemple, we use payload.id
to put a specific user data under this sub state.
// example of our users stateconst state = users: me: // id is used as key in our users sub state id: 'me' name: 'Arnaud' //...
To facilitate this common pattern, we use makePathReducer
.
/// in /module/user/messages.js const string = Typesconst DELETE_KEY = makePathReducer // here state manipulation is easier// state in reducer is directly `users.id` (in first exemple it was `users`)const user = Messenger // <- DO NOT FORGET TO CREATE YOUR MESSENGER INSTANCE user payloadid // or even simpler // ({ id }) => id
Selector & SliceSelector
reselect is a wonderfull library but it misses selector
with arguments
.
With Selector
you can send props to your selector
to make some filtering or choices.
You can ensure your props as Selector
is composed of PropTypes
.
Selector
used reselect under the hood and implements it's own memoization to handle props.
You can compose a Selector
with another Selector
(see getArticle
example)
A composed Selector
(see userSel
in getArticle
) return a partial function that is memoized once.
It is usefull for computation selection.
Do not use Selector
to get a slice of state.
Use SliceSelector
in this case.
As Selector
memoizes the last reducer
result, if you want to only get a portion of your state without any computation, it won't be performant to run memoization and props comparison check.
const state = "articles": "123": id: "123" author: "1" title: "My awesome blog post" comments: "324" "users": "1": "id": "1" "name": "Paul" "2": "id": "2" "name": "Nicole" "comments": "324": id: "324" "commenter": "2" const getSlice = statename const getUsers = const getUser = SliceSelector const getComments = statecomments const getComment = SliceSelector const getArticles = statearticles const getArticle = Selector