{"id":16918636,"url":"https://github.com/xnimorz/signal-middleware","last_synced_at":"2026-05-15T22:37:02.305Z","repository":{"id":57359329,"uuid":"133161496","full_name":"xnimorz/signal-middleware","owner":"xnimorz","description":"Redux signal middleware. A place to store your business logic and async code","archived":false,"fork":false,"pushed_at":"2018-08-06T09:11:39.000Z","size":677,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-13T09:53:55.086Z","etag":null,"topics":["async","async-actions","middleware","reactions","redux","redux-middleware"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xnimorz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-12T16:03:58.000Z","updated_at":"2023-07-31T09:26:38.000Z","dependencies_parsed_at":"2022-09-06T22:22:05.267Z","dependency_job_id":null,"html_url":"https://github.com/xnimorz/signal-middleware","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xnimorz/signal-middleware","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnimorz%2Fsignal-middleware","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnimorz%2Fsignal-middleware/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnimorz%2Fsignal-middleware/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnimorz%2Fsignal-middleware/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xnimorz","download_url":"https://codeload.github.com/xnimorz/signal-middleware/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnimorz%2Fsignal-middleware/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33082115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T20:25:35.270Z","status":"ssl_error","status_checked_at":"2026-05-15T20:25:34.732Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","async-actions","middleware","reactions","redux","redux-middleware"],"created_at":"2024-10-13T19:40:53.850Z","updated_at":"2026-05-15T22:37:02.284Z","avatar_url":"https://github.com/xnimorz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Signal Middleware\n\nSignal Middleware for Redux.\nExample: https://xnimorz.github.io/signal-middleware\n\n```\nnpm install --save signal-middleware\n```\n\nor\n\n```\nyarn add signal-middleware\n```\n\nSignal-middleware is created to give a place for async logic of your application and an abstraction between View and Data layers.\n\n# Contents\n\n- [Intro](#intro)\n- [Usage](#usage)\n  - [Getting started](#getting-started)\n  - [Async actions](#async-actions)\n  - [Getting state](#getting-state)\n  - [Async-await and business logic](#async-await-and-business-logic)\n- [Completed example](#completed-example)\n- [Motivation](#motivation)\n\n# Intro\n\nIn general, application can be divided into 3 parts:\n\n- view logic\n- business logic\n- data logic\n\nSignal action provides information between view and business layers.\nClassic action provides information between business and data layers.\nAlso you can always dispatch classic action from your view layer if no async actions are needed.\n\nHere is an image to represent the work:\n\n![MVC using signal-middleware](https://raw.githubusercontent.com/xnimorz/signal-middleware/master/resources/layers.png)\n\nThe business logic here is representented by signal-middleware reactions.\n\n# Usage:\n\n## Getting started\n\n1.  Import signalMiddleware to your store initialization file and add signalMiddleware to the list of middlewares:\n\n```javascript\nimport { createStore, applyMiddleware, combineReducers } from \"redux\";\nimport signalMiddleware from \"signal-middleware\";\n\nconst middlewares = [signalMiddleware /* Your other middlewares */];\n\nexport default function configureStore() {\n  const store = createStore(\n    combineReducers({\n      /* Your reducers */\n    }),\n    applyMiddleware(...middlewares)\n  );\n  return store;\n}\n```\n\n2.  Create a signal action:\n\n```javascript\nexport const SIGNAL_ACTION_KEY = \"SIGNAL_ACTION_KEY\";\n\nexport const signalActionCreator = data =\u003e ({\n  signal: SIGNAL_ACTION_KEY,\n  payload: data\n});\n```\n\n3.  Add a reaction to the `SIGNAL_ACTION_KEY` signal:\n\n```javascript\nimport { addReaction } from \"signal-middleware\";\n\naddReaction(SIGNAL_ACTION_KEY, ({ getState, dispatch }, payload) =\u003e {\n  // Paste your code here\n  // Dispatch new action via dispatch\n  // Get your current store state via getState\n  // Your action data is in payload\n  // You can return a promise from this function, to handle it from dispatch e.g. dispatch({signal: SIGNAL_ACTION_KEY}).then(() =\u003e {do some})\n});\n```\n\nSignal-middleware adds abstraction between View and Data layers.\n\n## Async actions\n\nSignal-middleware allows you to create async functions:\n\n```javascript\nimport signalMiddleware, { addReaction } from \"signal-middleware\";\n\nconst ADD_TODO = \"ADD_TODO\";\nconst RECEIVE_TODO = \"RECEIVE_TODO\";\n\n// Signal Action is an action that has `signal` field instead of `type`\nconst addTodoSignal = text =\u003e ({\n  signal: ADD_TODO,\n  payload: { text }\n});\n\n// Classic action works with store\nconst receiveTodo = text =\u003e ({\n  type: RECEIVE_TODO,\n  payload: { text }\n});\n\n// Add reaction to ADD_TODO signal\naddReaction(ADD_TODO, ({ dispatch }, { text }) =\u003e {\n  setTimeout(() =\u003e {\n    // Here we can invoke actions using dispatch\n    dispatch(receiveTodo(text));\n  }, 1000);\n});\n```\n\n## Getting state\n\nSignal-middleware provides `{ dispatch, getState }` object as the first argument of your reaction. So you can get access to him.\n\nLet's assume we shouldn't add todos which already have the same text:\n\n```javascript\nimport signalMiddleware, { addReaction } from \"signal-middleware\";\n\nconst ADD_TODO = \"ADD_TODO\";\nconst RECEIVE_TODO = \"RECEIVE_TODO\";\n\n// Signal Action is an action that has `signal` field instead of `type`\nconst addTodoSignal = text =\u003e ({\n  signal: ADD_TODO,\n  payload: { text }\n});\n\n// Classic action works with store\nconst receiveTodo = text =\u003e ({\n  type: RECEIVE_TODO,\n  payload: { text }\n});\n\n// Reactions are the good place for your project business logic.\n// 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.\naddReaction(ADD_TODO, ({ dispatch, getState }, { text }) =\u003e {\n  setTimeout(() =\u003e {\n    // Getting our current state\n    if (getState().todos.some(todo =\u003e todo.text === text)) {\n      return;\n    }\n    // Here we can invoke actions using dispatch\n    dispatch(receiveTodo(text));\n  }, 1000);\n});\n```\n\n## Async-await and business logic\n\nLet's add to our example some async logic and errors handling:\n\n```javascript\nimport signalMiddleware, { addReaction } from \"signal-middleware\";\n\nconst ADD_TODO = \"ADD_TODO\";\nconst RECEIVE_TODO = \"RECEIVE_TODO\";\nconst PENDING_TODO = \"PENDING_TODO\";\nconst FAIL_TODO = \"FAIL_TODO\";\n\n// Signal Action is an action that has `signal` field instead of `type`\nconst addTodoSignal = text =\u003e ({\n  signal: ADD_TODO,\n  payload: { text }\n});\n\n// Classic action works with store\nconst receiveTodo = text =\u003e ({\n  type: RECEIVE_TODO,\n  payload: { text }\n});\n\nconst pendingTodo = () =\u003e ({\n  type: PENDING_TODO\n});\n\n// Reactions are the good place for your project business logic.\n// 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.\naddReaction(ADD_TODO, async ({ dispatch, getState }, { text }) =\u003e {\n  try {\n    // You can dispatch any number of actions\n    dispatch(pendingTodo(result));\n    const result = await axios.post(REMOTE_URL_FOR_TODOS, { text });\n    dispatch(receiveTodo(result));\n  } catch (e) {\n    dispatch({ type: FAIL_TODO, payload: e });\n  }\n});\n```\n\nThe last example shows us how we can implement the business logic using `signal-middleware`\n\n# Completed example\n\n### Postpone callback after async request completes\n\nWhen 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:\n\n1.  Return a promise from signal handler directly: https://github.com/xnimorz/signal-middleware/master/examples/src/components/AddComment.js (with direct Promise wrapping)\n2.  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:\n\n- View: https://github.com/xnimorz/signal-middleware/master/examples/src/components/Areas.js\n- Logic: https://github.com/xnimorz/signal-middleware/master/examples/src/models/areas.js\n\nLet's write a file with actions and actionCreators:\n\n```javascript\n// actions/comments.js\n\nexport const ADD_COMMENT_SIGNAL = \"ADD_COMMENT_SIGNAL\";\nexport const RECEIVE_NEW_COMMENT = \"RECEIVE_NEW_COMMENT\";\nexport const REQUEST_ADD_COMMENT = \"REQUEST_ADD_COMMENT\";\n\nexport const addComment = comment =\u003e ({\n  signal: ADD_COMMENT_SIGNAL,\n  payload: comment\n});\n\nexport const receiveComment = comments =\u003e ({\n  type: RECEIVE_NEW_COMMENT,\n  payload: comments\n});\n\nexport const requestComment = () =\u003e ({\n  type: REQUEST_ADD_COMMENT\n});\n```\n\nNow we can handle `ADD_COMMENT_SIGNAL` using addReaction:\n\n```javascript\n// models/comments.js\nimport { DIRTY, FETCH, CLEAR } from \"../constants/status\";\nimport axios from \"axios\";\nimport {\n  RECEIVE_NEW_COMMENT,\n  REQUEST_ADD_COMMENT,\n  ADD_COMMENT_SIGNAL,\n  receiveComment,\n  requestComment\n} from \"../actions/comments\";\n\nimport { addReaction } from \"signal-middleware\";\n\naddReaction(ADD_COMMENT_SIGNAL, async ({ getState, dispatch }, payload) =\u003e {\n  // You can dispatch as many actions in signalMiddleware as you need\n  dispatch(requestComment());\n\n  try {\n    const { data } = await axios.post(\"/url/to/comments\", { comment: payload });\n    const comment = { id: data.id, text: data.text };\n    // Dispatch new action to store\n    dispatch(receiveComment(comment));\n    // You can resolve or reject action and\n    // handle promise in view layer (look to AddComment.js component)\n    return comment;\n  } catch (e) {\n    return Promise.reject();\n  }\n});\n\nexport default function comments(state = { status: DIRTY, data: [] }, action) {\n  switch (action.type) {\n    case REQUEST_ADD_COMMENT: {\n      return {\n        ...state,\n        status: FETCH\n      };\n    }\n    case RECEIVE_NEW_COMMENT: {\n      return {\n        status: CLEAR,\n        data: [action.payload, ...state.data]\n      };\n    }\n    default:\n      return state;\n  }\n}\n```\n\nIn view layer we can handle a promise:\n\n```javascript\nimport React, { PureComponent } from \"react\";\nimport { connect } from \"react-redux\";\n\nimport TextArea from \"./TextArea\";\nimport Button from \"./Button\";\n\nimport { addComment } from \"../actions/comments\";\n\nclass AddComment extends PureComponent {\n  textArea = React.createRef();\n\n  addComment = () =\u003e {\n    // We created async function as signal handler. Signal handler result will be received as returned value from actions dispatching\n    // So we can clear field after async request comes from server\n    this.props\n      .addComment(this.textArea.current.value)\n      .then(() =\u003e (this.textArea.current.value = \"\"));\n  };\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cTextArea innerRef={this.textArea} /\u003e\n        \u003cButton onClick={this.addComment}\u003eAdd comment\u003c/Button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nexport default connect(\n  null,\n  { addComment }\n)(AddComment);\n```\n\n# Motivation\n\nNowadays, building complicated frontend application requires a plenty of business logic with server requests and so on.\nYou 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.\n\nSome 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\n\n# License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnimorz%2Fsignal-middleware","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxnimorz%2Fsignal-middleware","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnimorz%2Fsignal-middleware/lists"}