{"id":15552732,"url":"https://github.com/gabri3l/redux-slim-async","last_synced_at":"2025-11-11T22:49:53.901Z","repository":{"id":28303117,"uuid":"117325936","full_name":"Gabri3l/redux-slim-async","owner":"Gabri3l","description":":alien: A Redux middleware to ease the pain of tracking the status of an async action","archived":false,"fork":false,"pushed_at":"2023-04-20T04:06:31.000Z","size":499,"stargazers_count":39,"open_issues_count":2,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-21T12:13:29.940Z","etag":null,"topics":["async-actions","middleware","react","redux","redux-middleware"],"latest_commit_sha":null,"homepage":"","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/Gabri3l.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-01-13T08:05:54.000Z","updated_at":"2023-10-21T23:14:29.000Z","dependencies_parsed_at":"2023-01-14T08:35:21.285Z","dependency_job_id":null,"html_url":"https://github.com/Gabri3l/redux-slim-async","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabri3l%2Fredux-slim-async","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabri3l%2Fredux-slim-async/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabri3l%2Fredux-slim-async/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabri3l%2Fredux-slim-async/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Gabri3l","download_url":"https://codeload.github.com/Gabri3l/redux-slim-async/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246156025,"owners_count":20732357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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-actions","middleware","react","redux","redux-middleware"],"created_at":"2024-10-02T14:21:59.087Z","updated_at":"2025-11-11T22:49:53.771Z","avatar_url":"https://github.com/Gabri3l.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"200\" src=\"./redux-slim-async.png\" alt=\"redux-slim-async\"\u003e\n\u003c/p\u003e\n\n\n# Redux Slim Async\n[![build status](https://circleci.com/gh/Gabri3l/redux-slim-async.svg?style=shield\u0026circle-token=:circle-token)](https://circleci.com/gh/Gabri3l/redux-slim-async)\n[![npm version](https://img.shields.io/npm/v/redux-slim-async.svg?style=flat-square)](https://www.npmjs.com/package/redux-slim-async)\n[![npm downloads](https://img.shields.io/npm/dm/redux-slim-async.svg?style=flat-square)](https://www.npmjs.com/package/redux-slim-async)\n\nA [FSA](https://github.com/acdlite/flux-standard-action)-compliant Redux middleware to ease the pain of tracking the status of an async action. While the compliance seems to break for how the middleware is presented (e.g. it requires a field of `types` instead of just `type`), every action that is dispatched with it is fully FSA-compliant. You can think of this middleware as a more succint way to dispatch FSA-compliant actions that track asyn requests.\n\n## Install\n\nTo install simply run\n\n`npm install --save redux-slim-async`\n\nor\n\n`yarn add redux-slim-async`\n\nYou then need to enable the middleware with the `applyMiddleware()` method as follows:\n\n```js\nimport { applyMiddleware, createStore, compose } from 'redux';\nimport slimAsync from 'redux-slim-async';\nimport rootReducer from '../reducers';\n\nconst store = createStore(\n  rootReducer,\n  compose(applyMiddleware(slimAsync)),\n);\n\nexport default store;\n```\n\nSince version 1.3.0 you will be able to add options, through those you can specify each suffix that defines the state of your async request (read more at the bottom).\n\n## Problem\n\nWhen handling any kind of asyn requests in Redux most of the time we need to track the state of such request. This means we need to know when the action is pending, completed successfully or completed with errors. A common pattern for it that leverages `redux-thunk` is the following:\n\n```js\nimport {\n  FETCH_DATA_ERROR,\n  FETCH_DATA_PENDING,\n  FETCH_DATA_SUCCESS,\n} from 'constants/actionTypes';\n\nfunction fetchDataError(error) {\n  return {\n    type: FETCH_DATA_ERROR,\n    payload: error,\n  }\n}\n\nfunction fetchMyDataPending() {\n  return {\n    type: FETCH_DATA_PENDING,\n  };\n}\n\nfunction fetchMyDataSuccess(payload) {\n  return {\n    type: FETCH_DATA_SUCCESS,\n    payload,\n  }\n}\n\nfunction fetchData() {\n  return (dispatch) =\u003e {\n    dispatch(fetchDataPending());\n\n    fetch('https://myapi.com/mydata')\n      .then(res =\u003e res.json())\n      .then(data =\u003e dispatch(fetchMyDataSuccess(data)))\n      .catch(err =\u003e dispatch(fetchMyDataError(err)));\n  }\n}\n\n```\n\nAll of this boilerplate is required to make sure we track the whole process. On top of that we might want to have more power over such requests, we might want to prevent a call to the API if the data is already available in our state or we might want to format the data that is coming back from the API. This pattern feels quite tedious so I am proposing a middleware that extends what shown in the `redux` docs.\n\nThe `redux-slim-async` provides an intuitive and condensed interface with some nice added features.\n\n## Solution\n\nAfter the middleware has been plugged in you can use it almost like you would normally dispatch an action, to follow our previous example:\n\n```js\nfunction fetchData() {\n  return {\n    types: [\n      FETCH_DATA_PENDING,\n      FETCH_DATA_SUCCESS,\n      FETCH_DATA_ERROR,\n    ],\n    callAPI: fetch('https://myapi.com/mydata').then(res =\u003e res.json()),\n  };\n}\n```\n\nThis handles dispatching all the actions for different statuses: pending, error and success. You can then have your state manager listen to them and update the state accordingly.\n\nThere are a few additions on top of what we saw so far. You can define a function that is in charge of preventing the request to be submitted based on your state.\n\n```js\nfunction fetchData() {\n  return {\n    types: [\n      FETCH_DATA_PENDING,\n      FETCH_DATA_SUCCESS,\n      FETCH_DATA_ERROR,\n    ],\n    callAPI: fetch('https://myapi.com/mydata').then(res =\u003e res.json()),\n    shouldCallAPI: (state) =\u003e state.myData === null,\n  };\n}\n```\n\nThis simply makes sure the request is sent only if the condition returned by the `shouldCallAPI` function is `true`.\n\nAnother useful function is the `formatData` one. Given the data returned from the request you can manipulate it or format it before it is sent to the manager.\n\n```js\nfunction fetchData() {\n  return {\n    types: [\n      FETCH_DATA_PENDING,\n      FETCH_DATA_SUCCESS,\n      FETCH_DATA_ERROR,\n    ],\n    callAPI: () =\u003e fetch('https://myapi.com/mydata').then(res =\u003e res.json()),\n    shouldCallAPI: (state) =\u003e state.myData === null,\n    formatData: (data) =\u003e ({\n      favorites: data.favorites,\n      latestFavorite: data.latest_favorite,\n    }),\n  };\n}\n```\n\nAt the current state of the middleware these fields are added outside the payload, this does not conform with the Flux Standard Action directive (it is in the roadmap to make it so).\n\n\n## Concatenate actions with Promises or async/await\n\nWhen calling an action that uses this middleware, you can now use `.then` or `.catch` to concatenate other actions after the current one has been resolved. You then have access to the updated state after the relative success/fail action has been handled by the manager. In your component you will be able to do something like this:\n\n```js\n  import React from 'react';\n  ...\n    componentDidMount() {\n      this.props.actions.fetchMyData()\n        .then(managerState =\u003e this.doStuff(managerState.someStateField)))\n        .catch(managerState =\u003e this.doErrorStuff(managerState.someErrorStateField));\n    }\n  ...\n\n```\n\n## Update v1.3.0\n\nWith this updae boilerplate code is reduce even more! Instead of forcing to pass an array of types every time we need to dispatch an aync action, there is now the possibility to define options at initiation time. This means that you can set each suffix you will be using to track `pending`, `success` or `error` status ahead of time. You can do so as follows:\n\n```js\nimport { applyMiddleware, createStore, compose } from 'redux';\nimport slimAsync from 'redux-slim-async';\nimport rootReducer from '../reducers';\n\nconst store = createStore(\n  rootReducer,\n  compose(applyMiddleware(slimAsync.withOptions({\n    pendingSuffix: '_PENDING',\n    successSuffix: '_SUCCESS',\n    errorSuffix: '_ERROR',\n  }))),\n);\n\nexport default store;\n```\n\nThis will allow you to define only the action prefix that is shared across every action dispatched to track the async request status. You will now be able to write:\n\n```js\nfunction fetchData() {\n  return {\n    typePrefix: FETCH_DATA,\n    callAPI: fetch('https://myapi.com/mydata').then(res =\u003e res.json()),\n    shouldCallAPI: (state) =\u003e state.myData === null,\n    formatData: (data) =\u003e ({\n      favorites: data.favorites,\n      latestFavorite: data.latest_favorite,\n    }),\n  };\n}\n```\n\nThe reason why it's called `typePrefix` instead of `type` is to simply avoid confusion. If the field was named `type` like a normal action, I would expect to be able to update the state manager once an action with that exact type has been dispatched, which would never happen. `typePrefix` makes it more clear that there's something more to it as that string only represent the prefix of the full action `type`. This is also the reason why such behavior is provided only when `options` are provided to the middleware.\n\nAnother available option is the one that specifies if the actions should be FSA compliant or not. Such option\nis `true` by default but, when set to false, the payload is directly injected in the body of the action object.\nThis means that instead of having an action in the shape of:\n\n```js\n// This is FSA compliant\n\n{\n  type: 'FETCH_DATA_SUCCESS'\n  payload: {\n    entry: \"some data\",\n    anotherEntry: \"some other data\",\n  },\n}\n\n// This is not FSA compliant\n{\n  type: 'FETCH_DATA_SUCCESS'\n  entry: \"some data\",\n  anotherEntry: \"some other data\",\n}\n```\n\n## RoadMap\n\n- [x] Add test suite\n- [x] Add continuous integration\n- [x] Make the middleware compliant to FSA directives\n- [x] Use FSA directives to skip action if not FSA compliant\n- [x] Allow to dispatch other actions after the current one has succeded or errored out\n- [x] Allow setting up a custom suffix for action types at initiation time\n- [x] Allow to use middleware even if not FSA compliant at initation time\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabri3l%2Fredux-slim-async","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabri3l%2Fredux-slim-async","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabri3l%2Fredux-slim-async/lists"}