{"id":13990870,"url":"https://github.com/matthieu-beteille/redux-data-fx","last_synced_at":"2025-04-15T23:25:09.204Z","repository":{"id":57350493,"uuid":"107654691","full_name":"matthieu-beteille/redux-data-fx","owner":"matthieu-beteille","description":"Declarative Side Effects for Redux","archived":false,"fork":false,"pushed_at":"2017-12-23T15:26:36.000Z","size":343,"stargazers_count":53,"open_issues_count":1,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-20T19:47:31.535Z","etag":null,"topics":["declarative","javascript","react","redux","side-effects"],"latest_commit_sha":null,"homepage":"https://matthieu-beteille.github.io/redux-data-fx/","language":"TypeScript","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/matthieu-beteille.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"code-of-conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-20T08:50:27.000Z","updated_at":"2021-06-30T15:09:53.000Z","dependencies_parsed_at":"2022-09-16T21:00:58.244Z","dependency_job_id":null,"html_url":"https://github.com/matthieu-beteille/redux-data-fx","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthieu-beteille%2Fredux-data-fx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthieu-beteille%2Fredux-data-fx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthieu-beteille%2Fredux-data-fx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthieu-beteille%2Fredux-data-fx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matthieu-beteille","download_url":"https://codeload.github.com/matthieu-beteille/redux-data-fx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240351222,"owners_count":19787794,"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":["declarative","javascript","react","redux","side-effects"],"created_at":"2024-08-09T13:03:26.298Z","updated_at":"2025-02-28T18:31:30.455Z","avatar_url":"https://github.com/matthieu-beteille.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Marks","Uncategorized"],"sub_categories":["[React - A JavaScript library for building user interfaces](http://facebook.github.io/react)","Uncategorized"],"readme":"# Redux Data FX \n\nDeclarative Side Effects for Redux.\n\nIt helps you keep your business logic and effectful code separate.\n\nThe idea is simple: in addition of your app's new state, your reducers can also return a data structure describing some side effects you want to run.\n \nThis takes inspiration from the elm architecture but this very implementation's idea comes from [re-frame](https://github.com/Day8/re-frame) in cljs and its effectful handlers. ([re-frame](https://github.com/Day8/re-frame) is an awesome project, you should definitely check it out). \n\n## Overview\n\n\u003cbr /\u003e\n\n![Redux Data FX Flow](flow.png)\n\n\u003cbr/\u003e\n\nThe same way an action represents an intent to update your app's state, an effect description is a declarative intent to perform a side effect.\nThe actual side effects are performed at the border of the system by effect handlers. \n\nEffect handlers are going to perform the \"effectful\" code interacting with the world (http calls, ...), using the browser's APIs (setTimeout, local storage, etc...), and they can feed data back in the redux loop by dispatching actions.\nThis way your reducers remain pure functions and it's easy to test that your effect description is correct since they are pure data. (see testing section)\n\nThere is a lot of cool tools that can be built around this idea. We can keep track of every effect description to get a clear idea of what has happened at the border of our system (log them, save them, re-perform them, etc...). \n\n## How does that that work?\n\nUsual reducer signature is:\n\n```(Action, State) -\u003e State```\n\nWith redux-data-fx, it becomes:\n\n```(Action, State) -\u003e State | { state: State, effects: Effects }```\n\nYour reducer can either return only a new state, or a combination of a new state and another data structure: the description of some side effects that you want to run.\n\n### 1. Declaratively describe side effects in your reducers.\n\nOne of your reducer could look like this:\n\n```javascript\nimport { fx } from 'redux-data-fx';\n\nfunction reducer(state = initialState, action) {\n  switch(action.type) {\n    'noop': \n      return state;\n    \n    'fetch/success': \n      return { \n        ...state, \n        data: action.payload.data, \n        isFetching: false \n      };\n\n    'fetch/error': \n      return { \n        ...state, \n        error: 'Oops something wrong happened...',\n        isFetching: false \n      };\n\n    'fetch-some-data':\n      return fx(\n        { ...state, isFetching: true },\n        [ \n          { \n            effect: 'fetch',           \n            url: 'http://some-api.com/data/1',\n            method: 'GET',\n            onSuccess: 'fetch/success',\n            onError: 'fetch/error'\n          } \n        ]\n      );\n\n    default:\n      return state;\n  }\n}\n```\n\nThe action 'fetch-some-data' is what we call an effectful action, it updates the state and returns a description of some side effects to run (here an http call).\n\nIf we want to run some side effects we need to return the result of the `fx` function called with our app new state and a data structure describing the side effects we want to perform.\n\n```javascript\nfx(NewState, Effects)\n```\n\n- *NewState:* the new state of our app (what you usually return from our reducer)\n\n- *Effects:* an array containing the descriptions of the side effects you want to run. Each side effect should be described by a map containing at least an 'effect' key, being the id of the effect you want to perform. The data required to actually perform the side effect can be passed through any other keys in the map. (for instance for an api call, you might want to provide the url, the HTTP method, and some parameters). That should remind you of the structure of a redux action: ```{ type: 'myAction1', ...params }```, except that the 'effect' key is used to identify the effect to perform: ```{ effect: 'myEffect1', ...params }```. \n\n*Note:* the fx function just creates an object of the following shape: \n```{ state: newAppState, effects: someEffectsToRun }```\nYou *have to* use the ```fx``` function to create this structure just so ```redux-data-fx``` knows that you want to run some effects.\nThen ```redux-data-fx``` will update the state and run the effects behind the scene.\n\n### 2. Run side effects\n\nIn order to actually run these described side effects you'll need to register some effect handlers. This is where the effectful code will be run (at the border of the system).\n\nFor instance to run our fetch side effect we would register the following handler:\n\n```javascript\nstore.registerFX('fetch', (params, getState, dispatch) =\u003e {\n  fetch(params.url, {\n    method: params.method,\n    body: params.body,\n    ...,\n  }).then(res =\u003e dispatch({ type: params.onSuccess, payload: res }))\n  .catch(res =\u003e dispatch({ type: params.onError, payload: res }))\n});\n```\n\nThe first argument is the handler's id, it needs to be the same as the effect key you'll return in your reducer(s) to trigger this same effect. In this case 'fetch'.\n\nThe second argument is the effect handler, the function that will perform the side effect.\nThis function will be given 3 parameters when called:\n- the params provided in the effect map (from your reducer)\n- getState: useful if you need to access your state here\n- dispatch: so you can dispatch new actions from there\n\n### 3. How to use it?\n\nAs simple as this:\n\n```npm install --save redux-data-fx```\n\n```javascript\nimport { reduxDataFX } from 'redux-data-fx'\nimport someMiddleware from 'some-middleware';\n\nconst enhancer = compose(\n  applyMiddleware(someMiddleware),\n  reduxDataFX\n);\n\nconst store = createStore(reducer, initialState, enhancer);\n\n// or createStore(reducer, enhancer); if you don't want to provide the initialState here\n// or createStore(reducer, initialState, reduxDataFx); if no middleware\n\n// then you can register as many FX as you want\nstore.registerFX('fetch', (params, getState, dispatch) =\u003e {\n...\n});\n\nstore.registerFX('localStorage', (params, getState, dispatch) =\u003e {\n ...\n});\n\nstore.registerFX('dispatchLater', (params, getState, dispatch) =\u003e {\n ...\n});\n```\n\nYou can import ```createStore``` from 'redux'. But if you are using typescript you should import it from 'redux-data-fx' (it's the same thing except the types will be right).\n\n### Use with ```combineReducers```\n\nIf you want this to work with ```combineReducers``` from redux, you just have to use the one from ```redux-data-fx``` instead. You'll now be able to return effects from the reducers you're combining.\n\n```javascript\nimport { reduxDataFX, combineReducers } from 'redux-data-fx'\n\nconst reducer = combinerReducers({\n  reducer1: reducer1,\n  ...\n});\n\nconst store = createStore(reducer, reduxDataFx);\n```\n\n### ```store.replaceReducer```\n\nIf you want to replace some reducers (for lazyloading), you should use the new function ```store.replaceEffectfulReducer``` from your store.\n\n### Testing\n\nYou can keep testing your reducers the same way but when they return some effect descriptions you have now the ability to make sure these are right too. \n\nAs described before, the function ```fx(newState, effects)``` only creates an object with two fields: \n- state: the new state of your app\n- effects: your effects\n\nThose are only data, so it's quite easy for you to test both of them.\n\nThen you can test your effect handlers separately, to verify they run the side effects as expected given the right inputs.\n\n#### TODO: Default FX\n\nCreate some default effect handlers like: \n- fetch\n- localStorage\n- sessionStorage\n- dispatchLater\n- dispatch","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatthieu-beteille%2Fredux-data-fx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatthieu-beteille%2Fredux-data-fx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatthieu-beteille%2Fredux-data-fx/lists"}