{"id":15397303,"url":"https://github.com/bloomca/redux-tiles","last_synced_at":"2025-04-08T03:13:14.316Z","repository":{"id":57351777,"uuid":"92570507","full_name":"Bloomca/redux-tiles","owner":"Bloomca","description":"Composable way to create less verbose Redux code","archived":false,"fork":false,"pushed_at":"2017-12-20T20:51:19.000Z","size":1240,"stargazers_count":236,"open_issues_count":4,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-31T18:20:05.551Z","etag":null,"topics":["redux","redux-middleware","redux-modules","redux-tiles","selector","state-management","typescript"],"latest_commit_sha":null,"homepage":"https://redux-tiles.js.org/","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/Bloomca.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":"2017-05-27T04:23:05.000Z","updated_at":"2024-01-19T20:30:41.000Z","dependencies_parsed_at":"2022-09-19T16:21:11.468Z","dependency_job_id":null,"html_url":"https://github.com/Bloomca/redux-tiles","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bloomca%2Fredux-tiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bloomca%2Fredux-tiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bloomca%2Fredux-tiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bloomca%2Fredux-tiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bloomca","download_url":"https://codeload.github.com/Bloomca/redux-tiles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247767236,"owners_count":20992548,"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":["redux","redux-middleware","redux-modules","redux-tiles","selector","state-management","typescript"],"created_at":"2024-10-01T15:36:57.296Z","updated_at":"2025-04-08T03:13:14.288Z","avatar_url":"https://github.com/Bloomca.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redux-Tiles\n\n[![Build Status](https://travis-ci.org/Bloomca/redux-tiles.svg?branch=master)](https://travis-ci.org/Bloomca/redux-tiles)\n[![npm version](https://badge.fury.io/js/redux-tiles.svg)](https://badge.fury.io/js/redux-tiles)\n[![Coverage Status](https://coveralls.io/repos/github/Bloomca/redux-tiles/badge.svg?branch=master)](https://coveralls.io/github/Bloomca/redux-tiles?branch=master)\n[![dependencies Status](https://david-dm.org/bloomca/redux-tiles/status.svg)](https://david-dm.org/bloomca/redux-tiles)\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\n**[Documentation](https://redux-tiles.js.org/)**\n\nRedux is an awesome library to keep state management sane on scale. The problem, though, is that it is toooo verbose, and often you'd feel like you are doing literally the same thing again and again. This library tries to provide minimal abstraction on top of Redux, to allow easy composability, easy async requests, and sane testability.\nIt is possible to start using this library [in existing project](https://bloomca.github.io/redux-tiles/advanced/integration.html), adding new functionality gradually.\n\u003e**[More about rationale behind this library](http://blog.bloomca.me/2017/06/02/why-i-created-redux-tiles-library.html)**\u003cbr\u003e\n\nFeatures:\n- built-in async handling\n- nesting (no several levels merging of state)\n- caching (call your requests declaratively, while only one will be fired)\n- semantic separation of reducers (no manual `combineReducers`)\n\n\u003e You can see [comparison between redux-tiles and vanilla redux](https://bloomca.github.io/redux-tiles-playground/)\n\u003e\n\u003e**[Examples](./examples)**\n\u003e * [Calculator](./examples/calculator)\n\u003e * [TodoMVC](./examples/todomvc)\n\u003e * [Hacker News API](./examples/hacker-news-api)\n\u003e * [Github API](./examples/github-api)\n\n## Installation\n\nTo install latest stable version, run:\n```shell\nnpm install --save redux-tiles\n```\n\nThis package was built with the idea in mind, that people will use it usually using some bundling tool – [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Rollup](http://rollupjs.org/). The package itself is written in TypeScript, and therefore provides typings out of the box.\n\nIf you for some reason don't use bundler, you can use UMD builds, which are located in [dist folder](https://unpkg.com/redux-tiles@0.6.1/dist/). Just include it in your page via `script` tag, and then you will have it under `window.ReduxTiles` global variable.\n\n## TOC:\n\n- [Example of use](#user-content-example-of-use)\n- [Rationale](#user-content-rationale)\n- [Integration API](#user-content-integration-api)\n- [Tiles API](#user-content-tiles-api)\n- [Nesting](#user-content-nesting)\n- [Middleware](#user-content-middleware)\n- [Server-side Rendering](#user-content-server-side-rendering)\n- [Selectors](#user-content-selectors)\n- [Tests](#user-content-tests)\n\n## Example of use\n\n\u003e [More comprehensive example](https://redux-tiles.js.org/introduction/Example.html)\n\n```javascript\nimport { createTile, createSyncTile } from 'redux-tiles';\n\n// sync tile to store information without any async stuff\nconst loginStatus = createSyncTile({\n  type: ['user', 'loginStatus'],\n  fn: ({ params: status }) =\u003e ({\n    status,\n    timestamp: Date.now(),\n  }),\n});\n\n// request to the server\n// it is absolutely separated, so it is very easy\n// to compose different requests\nconst authRequest = createTile({\n  type: ['user', 'authRequest'],\n  // we have access to dispatch, actions, selectors, etc –\n  // we can pass all what we need when creating middleware\n  // it allows us to test easier, and also compose other tiles\n  fn: ({ params, api, dispatch, actions, getState }) =\u003e\n    api.post('/login', params),\n});\n\n// actual business logic\n// note that we don't use direct `api` calls here\n// we just compose other basic tiles\nconst authUser = createTile({\n  type: ['user', 'auth'],\n  fn: async ({ params, dispatch, actions, selectors, getState }) =\u003e {\n    // login user\n    const { data: { id }, error } = await dispatch(actions.tiles.user.authRequest(params));\n\n    if (error) {\n      throw new Error(error);\n    }\n\n    // set up synchronously user status\n    dispatch(actions.tiles.user.loginStatus(true));\n\n    return true;\n  },\n});\n```\n\n## Rationale\n\nThere are enough projects around to keep your state management clean (for [example](https://github.com/erikras/ducks-modular-redux)), but they are mostly about organizing, rather than removing burden of repetitive stuff from the developer. Other packages offer you full-fledge integration with REST-API, [normalizing](https://github.com/paularmstrong/normalizr) your entities, building relations between models, etc. There is nothing like this here – in fact, if you need something like this, with the ability to query your local \"database\", I highly advise you to create your own solution, which will be custom-tailored to your specific problem.\n\nThis package focuses on very basic blocks, which are good for pretty simple applications (e.g. login/logout, fetch client data, set up calculator values).\n\n## Integration API\n\nDespite being easy-to-use package to write new modules, you'd have to do some work to integrate it into your project. In a nutshell, you have to have a middleware which will handle returned functions from dispatched actions (one is provided in this package, but [redux-thunk](https://github.com/gaearon/redux-thunk) will suffice as well), and then you have to combine all modules to create actions \u0026 reducers.\nIt is better to see in a small example:\n\n```javascript\nimport { createTile, createEntities, createMiddleware } from 'redux-tiles';\nimport { createStore, applyMiddleware } from 'redux';\n\nconst clientDataTile = createTile({\n  type: ['client', 'data'],\n  fn: ({ api, params}) =\u003e api.get('/client/info'),\n});\n\nconst tiles = [\n  clientDataTile,\n];\n\nconst { actions, reducer, selectors } = createEntities(tiles);\n\n// we inject `actions` and `selectors` into middleware, so they\n// will be available inside `fn` function of all tiles\nconst { middleware } = createMiddleware({ actions, selectors });\n\ncreateStore(reducer, applyMiddleware(middleware));\n```\n\n## Tiles API\n\nTiles are the heart of this library. They are intended to be very easy to use, compose and to test.\nThere are two types of tiles – asynchronous and synchronous. Modern applications are very dynamic, so async ones will be likely used more often. Also, don't constrain yourself into the mindset that async tiles are only for API communication – it might be anything, which involves some asynchronous interaction (as well as composing other tiles) – for instance, long polling implementation.\n\u003e [Full documentation for async tiles](https://redux-tiles.js.org/api/createTile.html)\n```javascript\nimport { createTile } from 'redux-tiles';\n\nconst photos = createTile({\n  // they will be structured api.photos inside redux state,\n  // and also available under actions and selectors as:\n  // actions.tiles.api.photos\n  type: ['api', 'photos'],\n\n\n  // params is an object with which we dispatch the action\n  // you can pass only one parameter, so keep it as an object\n  // with different properties\n  //\n  // all other properties are from your middleware\n  // fn expects promise out of this function\n  fn: ({ params, api }) =\u003e api.get('/photos', params),\n\n\n  // to nest data:\n  // { 5:\n  //    10: {\n  //      isPending: true,\n  //      fetched: false,\n  //      data: null,\n  //      error: null,\n  //   },\n  // },\n  // if you save under the same nesting array, data will be replaced\n  // other branches will be merged\n  nesting: (params) =\u003e [params.page, params.count],\n\n\n  // unless we will invoke with second parameter object with asyncForce: true,\n  // it won't be requested again\n  // dispatch(actions.tiles.api.photos(params, { asyncForce: true }))\n  caching: true,\n});\n```\n\nWe also sometimes want to keep some sync info (e.g. list of notifications), or we want to store some numbers for calculator, or active filters ([todoMVC](http://todomvc.com/) is a good example of a lot of synchronous operations). In this situation we will use `createSyncTile`, which has no meta data like `isPending`, `error` or `fetched`, but keeps all returned data from a function directly in state.\n\n\u003e [Full documentation for sync tiles](https://redux-tiles.js.org/api/createSyncTile.html)\n\n```javascript\nimport { createSyncTile } from 'redux-tiles';\n\nconst notifications = createSyncTile({\n  type: ['notifications'],\n\n\n  // all parameters are the same as in async tile\n  fn: ({ params, dispatch, actions }) =\u003e {\n    // we can dispatch async actions – but we can't wait\n    // for it inside sync tiles\n    dispatch(actions.tiles.user.dismissTerms());\n\n    return {\n      type: params.type,\n      data: processData(params.data),\n    };\n  },\n\n  // alternatively, if you perform some actions on existing data,\n  // it might be useful to write more declarative actions\n  // they have exactly the same signature and dispatch returned data\n  // to the tile\n  fns: {\n    add: ({ params, getData, selectors, getState }) =\u003e {\n      // same as:\n      // const currentData = selectors.notifications(getState(), params);\n      const currentData = getData();\n\n      return {\n        ...currentData,\n        data: currentData.concat(params.data),\n      };\n    },\n  },\n\n  // you can pass initial state to sync tile\n  // please, be careful with it! if you use nesting, then\n  // you have to specify nested items (otherwise selectors will\n  // return undefined for your nested item)\n  initialState: {\n    terms: {\n      type: 'terms',\n      data: []\n    },\n  },\n\n  // nesting works the same way\n  nesting: ({ type }) =\u003e [type],\n});\n```\n\n## Nesting\n\n\u003e [Full documentation on nesting](https://redux-tiles.js.org/advanced/nesting.html)\n\nVery often we have to separate some info, and with canonical redux we have to write something like this:\n```javascript\ncase ACTION.SOME_CONSTANT:\n  return {\n    ...state,\n    [action.payload.id]: {\n      [action.payload.quantity]: {\n        ...state[action.payload.id],\n        ...action.payload.data,\n      },\n    },\n  };\n```\n\nOr with `Object.assign`, which will make it even less readable. This is a pretty common pattern, and also pretty error prone – so we have to cover such code with unit-tests, while in reality they don't do a lot of intrinsic logic – just merge. Of course, we can use something like `lodash.merge`, but it is not always suitable. In tiles we have `nesting` property, in which you can specify a function from which you can return an array of nested values. The same code as above:\n\n```javascript\nconst infoTile = createTile({\n  type: ['info', 'storage'],\n\n  // params here and in nesting are the same object\n  fn: ({ params: { quantity, id }, api }) =\u003e api.get('/storage', { quantity, id }),\n\n  // in the state they will be kept with the following structure:\n  // {\n  //   someId: {\n  //     5: {\n  //       isPending: true,\n  //       fetched: false,\n  //       data: null,\n  //       error: null,\n  //     },\n  //   },\n  // }\n  nesting: ({ quantity, id }) =\u003e [id, quantity],\n});\n```\n\n## Middleware\n\nIn order to use this library, you have to apply middleware, which will handle functions returned from dispatched actions. Very basic one is provided by this package:\n\n\u003e [Full documentation for middleware](https://redux-tiles.js.org/api/createMiddleware.html)\n```javascript\nimport { createMiddleware } from 'redux-tiles';\n\n// these are not required, but adding them allows you\n// to do Dependency Injection pattern, so it is easier to test\nimport actions from '../actions';\nimport selectors from '../selectors';\n\n// it is a good idea to put API layer inside middleware, so\n// you can easily separate client and server, for instance\nimport api from '../utils/api';\n\n\n// this object is optional. every property will be available inside\n// `fn` of all tiles\n// also, `waitTiles` is helpful for server-side-rendering\nconst { middleware, waitTiles } = createMiddleware({ actions, selectors, api });\napplyMiddleware(middleware);\n```\n\nAlso, [redux-thunk](https://github.com/gaearon/redux-thunk) is supported, and in order to pass your own properties you should [inject this object to redux-thunk](https://github.com/gaearon/redux-thunk#injecting-a-custom-argument). Also, there is nothing bad to just import actions and selectors on top of the files, but then testing might require much more mocking, which can make your tests more brittle.\n\n## Server-side Rendering\n\n\u003e [Article about SSR with prefetch](http://blog.bloomca.me/2017/06/11/server-side-rendering-with-prefetch.html)\n\nRedux-tiles support requests on the server side. In order to do that correctly, you are supposed to create actions for each request in Node.js. Redux-Tiles has caching for async requests (and keeps them inside middleware, so they are not shared between different user requests) – it keeps list of all active promises, so you might accidentaly share this part of the memory with other users!\n\nAlso, to make this part of functionality working, you have to use redux-tiles middleware, or pass `promisesStorage` object to redux-thunk additional object (more in [caching section in docs](https://redux-tiles.js.org/api/createTile.html#caching)).\n\n```javascript\nimport { createMiddleware, createEntities } from 'redux-tiles';\nimport { createStore, applyMiddleware } from 'redux';\nimport tiles from '../../common/tiles';\n\nconst { actions, reducer, selectors } = createEntities(tiles);\nconst { middleware, waitTiles } = createMiddleware({ actions, selectors });\nconst store = createStore(reducer, {}, applyMiddleware(middleware));\n\n// this is a futile render. It is needed only to kickstart requests\n// unfortunately, there is no way to avoid it\nrenderApplication(req);\n\n// wait for all requests which were fired during the render\nawait waitTiles();\n\n// this time you can safely render your application – all requests\n// which were in `componentWillMount` will be fullfilled\n// remember, `componentDidMount` is not fired on the server\nres.send(renderApplication(req));\n```\n\nThere is also a package [delounce](https://github.com/Bloomca/delounce), from where you can get `limit` function, which will render the application if requests are taking too long.\n\n## Selectors\n\n\u003e [Full documentation on selectors](https://redux-tiles.js.org/advanced/selectors.html)\n\nAll tiles provide selectors. After you've collected all tiles, invoke `createSelectors` function with possible change of default namespace, and after you can just use it based on the passed type:\n\n```javascript\nimport { createTile, createSelectors } from 'redux-tiles';\n\nconst tile = createTile({\n  type: ['user', 'auth'],\n  fn: ...,\n  nesting: ({ id }) =\u003e [id],\n});\n\nconst tiles = [tile];\n\nconst selectors = createSelectors(tiles);\n\n// second argument is params with which you dispatch action – it will get data\n// for corresponding nesting\nconst { isPending, fetched, data, error } = selectors.user.auth(state, { id: '456' });\n```\n\n## Tests\n\nAlmost all business logic will be contained in \"complex\" tiles, which don't do requests by themselves, rather dispatching other tiles, composing results from them. It is very important to pass all needed functions via middleware, so you can easily mock it without relying on other modules. All passed data is available in tiles via `reflect` property.\n\n```javascript\nimport { createTile } from 'redux-tiles';\n\nconst params = {\n  type: ['auth', 'token'],\n  fn: ({ api, params }) =\u003e api.post('/token', params),\n};\nconst tile = createTile(params);\n\n// same object\nassert(tile.reflect === params); // true\n```\n\n## Contributing\n\nAll suggestions or participating are welcome! If you have any idea about improving API, or bringing some common functionality, don't hesitate, but please create an issue.\nAlso, in case you really think something is missing or wrong, please create an issue first, where we will discuss the problem and possible solutions, and then we can agree on implementation details.\n\n## LICENSE\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloomca%2Fredux-tiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbloomca%2Fredux-tiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloomca%2Fredux-tiles/lists"}