Signal Middleware
Signal Middleware for Redux. Example: https://xnimorz.github.io/signal-middleware
npm install --save signal-middleware
or
yarn add signal-middleware
Signal-middleware is created to give a place for async logic of your application and an abstraction between View and Data layers.
Contents
Intro
In general, application can be divided into 3 parts:
- view logic
- business logic
- data logic
Signal action provides information between view and business layers. Classic action provides information between business and data layers. Also you can always dispatch classic action from your view layer if no async actions are needed.
Here is an image to represent the work:
The business logic here is representented by signal-middleware reactions.
Usage:
Getting started
- Import signalMiddleware to your store initialization file and add signalMiddleware to the list of middlewares:
;; const middlewares = signalMiddleware /* Your other middlewares */; { const store = ; return store;}
- Create a signal action:
const SIGNAL_ACTION_KEY = "SIGNAL_ACTION_KEY"; const signalActionCreator = signal: SIGNAL_ACTION_KEY payload: data;
- Add a reaction to the
SIGNAL_ACTION_KEY
signal:
; ;
Signal-middleware adds abstraction between View and Data layers.
Async actions
Signal-middleware allows you to create async functions:
; const ADD_TODO = "ADD_TODO";const RECEIVE_TODO = "RECEIVE_TODO"; // Signal Action is an action that has `signal` field instead of `type`const addTodoSignal = signal: ADD_TODO payload: text ; // Classic action works with storeconst receiveTodo = type: RECEIVE_TODO payload: text ; // Add reaction to ADD_TODO signal;
Getting state
Signal-middleware provides { dispatch, getState }
object as the first argument of your reaction. So you can get access to him.
Let's assume we shouldn't add todos which already have the same text:
; const ADD_TODO = "ADD_TODO";const RECEIVE_TODO = "RECEIVE_TODO"; // Signal Action is an action that has `signal` field instead of `type`const addTodoSignal = signal: ADD_TODO payload: text ; // Classic action works with storeconst receiveTodo = type: RECEIVE_TODO payload: text ; // Reactions are the good place for your project business logic.// It's separate from view and data logic. View layer works with business logic through the signal actions, and business logic layer works with data logic through the classic actions.;
Async-await and business logic
Let's add to our example some async logic and errors handling:
; const ADD_TODO = "ADD_TODO";const RECEIVE_TODO = "RECEIVE_TODO";const PENDING_TODO = "PENDING_TODO";const FAIL_TODO = "FAIL_TODO"; // Signal Action is an action that has `signal` field instead of `type`const addTodoSignal = signal: ADD_TODO payload: text ; // Classic action works with storeconst receiveTodo = type: RECEIVE_TODO payload: text ; const pendingTodo = type: PENDING_TODO; // Reactions are the good place for your project business logic.// It's separated from view and data logic. View layer works with business logic through the signal actions, and business logic layer works with data logic through the classic actions.;
The last example shows us how we can implement the business logic using signal-middleware
Completed example
Postpone callback after async request completes
When you write a comment you should clear the text field after server request completes. At the moment you don't know about future id of the comment, so you would add a callback after server request completes. When you use signal-middleware and dispatch a signal action you can use async-await
or directly return a promise from signal handler. Your view layer can subscribe to the promise using then
. You can see it in our examples:
- Return a promise from signal handler directly: https://github.com/xnimorz/signal-middleware/master/examples/src/components/AddComment.js (with direct Promise wrapping)
- Declare
async
function to wrap it with promise (becouse async-await functions return a promise). You can see this example below this text or here:
- View: https://github.com/xnimorz/signal-middleware/master/examples/src/components/Areas.js
- Logic: https://github.com/xnimorz/signal-middleware/master/examples/src/models/areas.js
Let's write a file with actions and actionCreators:
// actions/comments.js const ADD_COMMENT_SIGNAL = "ADD_COMMENT_SIGNAL";const RECEIVE_NEW_COMMENT = "RECEIVE_NEW_COMMENT";const REQUEST_ADD_COMMENT = "REQUEST_ADD_COMMENT"; const addComment = signal: ADD_COMMENT_SIGNAL payload: comment; const receiveComment = type: RECEIVE_NEW_COMMENT payload: comments; const requestComment = type: REQUEST_ADD_COMMENT;
Now we can handle ADD_COMMENT_SIGNAL
using addReaction:
// models/comments.js;;; ; ; { }
In view layer we can handle a promise:
;; ;; ; textArea = React; { // We created async function as signal handler. Signal handler result will be received as returned value from actions dispatching // So we can clear field after async request comes from server thisprops ; }; { return <div> <TextArea innerRef=thistextArea /> <Button onClick=thisaddComment>Add comment</Button> </div> ; } null addComment AddComment;
Motivation
Nowadays, building complicated frontend application requires a plenty of business logic with server requests and so on.
You can use middlewares such as redux-thunk
to implement async actions creator
. However after a definite time interval it would be complicated to work with tons of code in action creators
. For example, if you want to show an alert to user, when he clicks the button, you wouldn't patch browser engine code, you just add some logic to your own project. You can relate to action
similarly. Actions
in redux application are similar to events in browser. Consequently, if some event (action
) in your project is triggered, you have a reaction for the action
. The main goal of signal-middleware
is to give an abstraction for the implementation of a separate business logic.
Some more information about middlewares you can find in a lecture (Russian lang): https://docs.google.com/presentation/d/1qFTB--HrXCU0_nVQ_T4ZlB9CsiXpXQsASc14pctoggA/edit?usp=sharing
License
MIT