{"id":19074016,"url":"https://github.com/edtoken/redux-tide","last_synced_at":"2025-04-28T19:20:10.716Z","repository":{"id":57351786,"uuid":"110261400","full_name":"edtoken/redux-tide","owner":"edtoken","description":"Simple library for redux crud normalized state and actions/selectors for it","archived":false,"fork":false,"pushed_at":"2018-02-11T18:14:21.000Z","size":2644,"stargazers_count":20,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-28T19:20:05.032Z","etag":null,"topics":["axios","crud","crud-functionality","javascript","react","reactjs","redux","selector","storage"],"latest_commit_sha":null,"homepage":"https://edtoken.github.io/redux-tide","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/edtoken.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-10T15:21:20.000Z","updated_at":"2019-11-06T09:13:20.000Z","dependencies_parsed_at":"2022-09-18T23:51:10.452Z","dependency_job_id":null,"html_url":"https://github.com/edtoken/redux-tide","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtoken%2Fredux-tide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtoken%2Fredux-tide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtoken%2Fredux-tide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edtoken%2Fredux-tide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edtoken","download_url":"https://codeload.github.com/edtoken/redux-tide/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251372537,"owners_count":21578967,"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":["axios","crud","crud-functionality","javascript","react","reactjs","redux","selector","storage"],"created_at":"2024-11-09T01:49:27.102Z","updated_at":"2025-04-28T19:20:10.693Z","avatar_url":"https://github.com/edtoken.png","language":"JavaScript","readme":"# Redux tide\nSimple library for helping created redux-normalized state.  \nActionCreator + ActionSelector, reducers are created automatically    \n\n### Table of Contents\n- [Features](#features)\n- [Overview](#overview)\n- [Examples](#examples)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Action](#action)\n- [Reducer](#reducer)\n- [Middleware](#middleware)\n- [Selector](#selector)\n- [Contributions](#contributions)\n- [Changelog](#changelog)\n- [License](#license)\n\n\n[website](https://edtoken.github.io/redux-tide)  \n[docs](https://edtoken.github.io/redux-tide/docs)  \n[coverage-report](https://edtoken.github.io/redux-tide/coverage/lcov-report/index.html)  \n\n\n[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)  \n\n[![npm version](https://badge.fury.io/js/redux-tide.svg)](https://badge.fury.io/js/redux-tide)\n[![Build Status](https://api.travis-ci.org/edtoken/redux-tide.svg?branch=master)](https://travis-ci.org/edtoken/redux-tide)\n[![Maintainability](https://api.codeclimate.com/v1/badges/5952e9edfa038e49658f/maintainability)](https://codeclimate.com/github/edtoken/redux-tide/maintainability)\n[![npm downloads](https://img.shields.io/npm/dm/redux-tide.svg?style=flat-square)](https://www.npmjs.com/package/redux-tide)\n[![Coverage Status](https://coveralls.io/repos/github/edtoken/redux-tide/badge.svg?branch=master)](https://coveralls.io/github/edtoken/redux-tide?branch=master)\n[![Inline docs](https://inch-ci.org/github/edtoken/redux-tide.svg?branch=master)](https://inch-ci.org/github/edtoken/redux-tide)\n[![HitCount](http://hits.dwyl.com/edtoken/redux-tide.svg)](http://hits.dwyl.com/edtoken/redux-tide)\n\n[![NPM](https://nodei.co/npm/redux-tide.png?downloads=true\u0026downloadRank=true\u0026stars=true)](https://nodei.co/npm/redux-tide/)\n\n--- \n\n### Motivation\nYou don't need to create reducers for rest-api data  \nYou should create reducers only for business front-end logic  \n\n### Features\n1. Simple actionCreator for any of your async library\n2. Normalization state\n3. Auto create reducers\n4. Simple single selector for all your actions\n\n### Overview\n*Redux-Tide* - Do not force you to use only it,    \nThis is a small library helping you create normalization, actions, reducers and selector   \nYou can use it with any of other libraries  \nYou can add redux tide in any even old/new project  \nRedux tide - it's concept for save your backend data, normalize, and selector for it    \n```\n                                        ┌───────────┐\n                                        │   Some    │\n                                        │Reducers...│    ┌──────────┐\n                                        └───────────┘    │   Some   │    ┌───────────┐\n                                        ╔═══════════╗    │ Selector │    │   Some    │\n╔════════╗  ┌────────────┐  ┌────────┐┌▶║  Actions  ║    └──────────┘ ┌─▶│ Component │\n║ Action ╠──▶ Async Rest ├──▶Response├┤ ║   Meta    ╠─┐               │  └───────────┘\n╚════════╝  └────────────┘  └────────┘│ ╚═══════════╝ │  ╔══════════╗ │\n                                      │ ╔═══════════╗ ├─▶║ Selector ╠─┘\n                                      │ ║Normalized ║ │  ╚══════════╝\n                                      └▶║   Data    ║─┘\n                                        ╚═══════════╝\n```\n \n\n### Examples            \n[blog](https://edtoken.github.io/redux-tide/?ex=blog) - using with axios and REST api  \n[blog-source](https://github.com/edtoken/redux-tide/tree/master/website/src/blog) - blog demo source code    \n[different-entity-id-example](https://edtoken.github.io/redux-tide?ex=different-entity-id)    \n[different-entity-id-source](https://github.com/edtoken/redux-tide/tree/master/website/src/different-entity-id)  \n[merged-actions-data-example](https://edtoken.github.io/redux-tide?ex=merged-actions-data)    \n[merged-actions-data-source](https://github.com/edtoken/redux-tide/tree/master/website/src/merged-actions-data)  \n\n### Installation\nTo install the stable version:\n\n```\nnpm install redux-tide --save\n```\n\n#### Complementary Packages\nYour project should have\n[normalizr](https://github.com/paularmstrong/normalizr), [redux](https://redux.js.org/), [react-redux](https://github.com/reactjs/react-redux), [redux-thunk](https://github.com/gaearon/redux-thunk)\n```\nnpm install normalizr --save\nnpm install redux --save\nnpm install react-redux --save\nnpm install redux-thunk --save\n```\n\n------\n### Discussion\nYou can connect to [Gitter chat room](https://gitter.im/practice-feature/redux-tide)  \n[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n### Usage\n1. You might install library    \n```\nnpm install redux-tide --save \n```  \n2. Install [normalizr](https://github.com/paularmstrong/normalizr), [redux](https://redux.js.org/), [react-redux](https://github.com/reactjs/react-redux), [redux-thunk](https://github.com/gaearon/redux-thunk), you can use help [Complementary Packages](#complementary-packages)\n  \n3. Define entity-schema   \n    ```javascript\n    // entity-schema.js\n    import {schema} from 'normalizr'\n    \n    const postsSchema = new schema.Entity('posts')\n    const commentsSchema = new schema.Entity('comments')\n    \n    postsSchema.define({\n      comments: [commentsSchema]\n    })\n    commentsSchema.define({\n      post: postsSchema\n    })\n       \n    const appSchema = {\n      commentsSchema,\n      postsSchema\n    }\n            \n    export {\n        postsSchema,\n        commentsSchema,\n        appSchema\n    }\n         \n    ```\n\n4. Modify your store.js file \n    ```javascript\n    // store.js\n    import {denormalize} from 'normalizr';\n    import {createReducers, setDefaultResponseMapper, setDenormalize} from 'redux-tide';\n    import {appSchema} from './entity-schema'\n    \n    // required\n    setDenormalize(denormalize)\n    \n    // not required\n    setDefaultResponseMapper((resp) =\u003e {\n      // your response\n      return resp.data\n    })\n    \n    // your store\n    export default createStore(\n      combineReducers({\n        // your reducers\n        ...createReducers(...appSchema)\n      }),\n      initialState,\n      composedEnhancers\n    )\n        \n    ```\n5. Ready! Now you can use it. [Create action](#action)\n------\n### Discussion\nYou can connect to [Gitter chat room](https://gitter.im/practice-feature/redux-tide)  \n[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n### Action\nExample of RestApi class [source](https://github.com/edtoken/redux-tide/blob/master/website/src/RESTApi.js#L3)  \nYou can look *entity-schema.js* example from [Usage section](#usage)\n\n```javascript\nimport {createAction} from 'redux-tide';\nimport {del, get, post, put} from '../RESTApi'\nimport {postsSchema} from 'entity-schema';\nimport {OPEN_EDIT} from './action-types'\n\n/**\n* createAction argumnents\n*\n* @param {String|Object} actionSchema - normalizr actionSchema item\n* @param {Function|Promise} actionMethod\n* @param {String|Function} [queryBuilder=undefined]\n* @param {Function} [responseMapper=_defaultResponseMapper||callback from setDefaultResponseMapper]\n**/\n\n// simple action\nexport const getAllPosts = createAction(\n    postsSchema, \n    get, \n    `posts?_embed=comments\u0026_order=desc` // simple static url\n)\n\n// warning, please read \"Create one action for different entity id\" section\nexport const getPostById = createAction(\n    postsSchema, \n    get, \n    postId =\u003e `posts/${postId}?_embed=comments` // url with property postId\n)\n\n// warning, please read \"Create one action for different entity id\" section\nexport const delPostById = createAction(\n    postsSchema, \n    del, \n    postId =\u003e `posts/${postId}` // url with property postId\n)\n\n// warning, please read \"Create one action for different entity id\" section\nexport const updatePostById = createAction(\n    postsSchema, \n    put, \n    (postId, data) =\u003e [\n        `posts/${postId}`, // url with property\n        undefined, // query params\n        data // put body\n    ]\n)\n\nexport const createNewPost = createAction(\n    postsSchema, \n    post, \n    data =\u003e [\n        `posts`, // static url\n        undefined, // query params\n        data // post body\n    ]\n)\n\n// basic redux action can be use\nexport const openEditPost = (postId) =\u003e {\n  return {\n    type:OPEN_EDIT,\n    postId\n  }\n}\n\n```\n\n#### How to reset action store data?\nWhen you need to force a clear state of action, please use\n \n```javascript\ndispatch(createNewPost.empty())\n```\nOR \n```javascript\ndispatch(getPostById.withPrefix(props.postId).empty())\n```\n\n#### How to use one action with array of connected components ? \nPlease read [Create one action for different entity id](#create-one-action-for-different-entity-id) section and [Other options to create an action](#other-options-to-create-an-action)  \nAnd look examples:    \n[different-entity-id-example](https://edtoken.github.io/redux-tide?ex=different-entity-id)  \n[different-entity-id-source](https://github.com/edtoken/redux-tide/tree/master/website/src/different-entity-id)\n\n### Create one action for different entity id\nIf you want to create 1 action get || post || put || delete   \nfor work with single entity but multiple entity ids, **for example: `GET post/:postId`**     \n**You should be use action.withPrefix method** - it's generate new uniq action id and **new uniq action reducer state**  \n\n**For details you can look example:**   \n[different-entity-id-example](https://edtoken.github.io/redux-tide?ex=different-entity-id)  \n[different-entity-id-source](https://github.com/edtoken/redux-tide/tree/master/website/src/different-entity-id)\n\n*if you dont't make it - your next call* `dispatch(getPostById(nextPostId))`   \n*overwrite your preview call data*  `dispatch(getPostById(prevPostId))`\n  \n\n```javascript\n// actions.js\nimport {createAction} from 'redux-tide';\nimport {del, get, post, put} from '../RESTApi'\nimport {postsSchema} from 'entity-schema';\n\n// write main action\nexport const getPostById = createAction(\n    postsSchema, \n    get, \n    postId =\u003e `posts/${postId}?_embed=comments` // url with property postId\n)\n\n// component.js\nimport {getActionData} from 'redux-tide';\nimport {getPostById} from './actions';\n\n// WRONG connect!\nexport default connect(\n  // your selector does not have uniq post Id, so data is rewrited\n  (state, props) =\u003e getActionData(getPostById),\n  dispatch =\u003e {\n    // your selector does not have uniq post Id, so data is rewrited\n    fetch: (postId) =\u003e dispatch(getPostById(postId))\n  }\n)(SomeComponent)\n\n// CORRECT connect\n// you can use this connect with different postId\nexport default connect(\n  // selector get state of getPostById but reducer key named with postId\n  (state, props) =\u003e getActionData(getPostById.withPrefix(props.postId)), \n  dispatch =\u003e {\n    // action call getPostById but dispatch TYPE make with prefix postId\n    fetch: (postId) =\u003e dispatch(getPostById.withPrefix(postId)(postId))\n  }\n)(SomeComponent)\n\n// common-component.js\nimport React, {Component} from 'react'\nimport {PostFormComponent} from './component'\n\nexport default class ComponentWrapper extends Component {\n  render(){\n    return(\u003cPostFormComponent postId={this.props.postId}/\u003e)\n  }\n}\n```\n\n### Selector\n```javascript\nimport {getActionData} from 'redux-tide';\n```\n\n```\n{String} actionId - your action id\n{*} sourceResult - your source response from server (not mapped response)\n{String} status - pending|success|error\n{Number} time - timestamp of action\n{Boolean} hasError - has error or not\n{String} errorText - text of error\n{Boolean} isFetching - status === 'pending'\n{Object|Array} payload - denormalized response for current action\n{Object|Array} prevPayload - denormalized previous response\n \n```\n\n```javascript\nimport {getActionData} from 'redux-tide';\nimport {\n    createNewPost, \n    delPostById, \n    getAllPosts, \n    openEditPost\n} from './actions';\n\nexport default connect(\n  // single selector function for all your actions\n  (state, props) =\u003e getActionData(getAllPosts)\n)(SomeComponent)\n\nexport default connect(\n  (state, props) =\u003e ({\n    table: getActionData(getAllPosts)(state, props)\n  })\n)(SomeComponent)\n\n// create custom selectors\nconst makeGetMergedActionData = (postId) =\u003e {\n    return createSelector(\n      [\n        getActionData(updatePostById.withPrefix(postId)),\n        getActionData(getPostById.withPrefix(postId)),\n        someSelector // your selector\n      ],\n      (updateData, fetchData, someData) =\u003e {\n\n        updateData = updateData || {}\n        fetchData = fetchData || {}\n\n        const sortedDataByUpdateTime = [updateData, fetchData].sort(item =\u003e item.time)\n\n        return sortedDataByUpdateTime.reduce((memo, item) =\u003e {\n          return Object.assign(memo, item)\n        }, {someData})\n      }\n    )\n    \n```\n\n\n### Middleware\n```javascript\nimport {\n    createNewPost, \n    delPostById, \n    getAllPosts, \n    openEditPost\n} from './actions';\n\nconst createNewPostActionId = createNewPost.actionId()\n\n// delete post using with prefix (post id query parameter), so need check parentActionId\nconst delPostByIdParentActionId = delPostById.actionId() \n\n// action id if you called delPostById.withPrefix(postId), where postId === 5\nconst delPostId5ActionId = delPostById.withPrefix(5).actionId()\n\nexport const middleware = store =\u003e next =\u003e action =\u003e {\n       \n    const result = next(action)\n    \n    switch (action.actionId) {\n        // all actions createNew post\n        case createNewPostActionId:\n            if (action.status === 'success') {\n                store.dispatch(openEditPost(action.payload))\n                store.dispatch(getAllPosts())\n            }\n            break\n            \n        case delPostId5ActionId:\n            if (action.status === 'success') {\n                // post with id 5 is was deleted success \n            }\n        \n    }\n       \n    // we used action delPostById with prefix postId\n    // for example dispatch(delPostById.withPrefix(postId)(postId)\n    // so, actionId has postId in actionId\n    // and if you want track all calls with any postId you hould used parentActionId property\n    // parentActionId - it's actionId from action which was called .withPrefix(*)\n    // so if you called delPostById.withPrefix()..., parrent action id - it's action id delPostById\n    \n    switch (action.parentActionId) {\n        case delPostByIdParentActionId:\n            if (action.status === 'success') {\n                store.dispatch(getAllPosts())\n                \n                let delPostCurrentActionId = delPostById.withPrefix(action.payload).actionId()\n                // it's actionid current deleted post === action.actionId\n                \n            }\n            break\n    }\n}\n\n```\n\n## Other options to create an action\n\n#### How to use `.withPrefix`, `.withName`, `.clone` methods?\n**For details you can look example:**   \n[different-entity-id-example](https://edtoken.github.io/redux-tide?ex=different-entity-id)   \n[different-entity-id-source](https://github.com/edtoken/redux-tide/tree/master/website/src/different-entity-id)\n  \nThis methods returns same action     \nBut generate new uniq dispatch type and new uniq action state    \nYou should be call `.withPrefix`, `.withName`, `.clone` when you are dispatch event and use getActionData\n```javascript\n\ndispatch(getUserAction.withPrefix(userId)(userId))\nconnect(\n  (state)=\u003e getActionData(getUserAction.withPrefix(userId))\n)\n\n// And this methods can chain calls\nexport const getUserAction = createAction(\n    user, \n    get, \n    'user', \n    (resp) =\u003e {resp.data}\n).withName('user')\n\ndispatch(getUserAction.withPrefix(userId)) // action type id and state key name includes user + userId\n\n// OR\ndispatch(getUser.withName('user').withPrefix(userId))\n\n// AND selector\ngetActionData(getUser.withName('user').withPrefix(userId))\n```\n\n#### Custom server response mapper\n```javascript\n// calling url 'user' but replace backend success response to resp.data\n// You always can be get source response data \n// from selector getActionData property sourceResult\nexport const getUserAction = createAction(\n    user, \n    get, \n    'user', \n    (resp) =\u003e {resp.data}\n)\n```\n\n#### Call dispatch or getState in query builder method\n```javascript\n\n// you can pass multi level functions or promises \n// (args) =\u003e (dispatch, getState) =\u003e (dispatch, getState) =\u003e (dispatch, getState) =\u003e ...\n// calling url 'user/${userId}'\n\nexport const getUserAction = createAction(\n    user, \n    get, \n    (userId) =\u003e {\n        return (dispatch, getState)=\u003e{\n            // return Promise (axios call or other)\n        }\n    }\n)\n```\n### Whats else?\n\n```javascript\n// actions.js \n\nexport const get = (url) =\u003e {// returns Promise ajax call}\n\n// simple action used custom method for getting data\nexport const getUserAction = createAction(user, () =\u003e {\n return new Promise((resolve, reject) =\u003e {\n   // cookie|local storage|other get data\n   resolve({\n     //data\n   })\n })\n})\n\n// if you want to best action store name in redux, \n// you should used this pattern\n// calling url 'user/${userId}'\nexport const getUserAction = createAction(\n    user, \n    get, \n    (userId) =\u003e `user/${userId}`\n).withName('UsersAction')\n\n\n// calling url 'user/${userId}' \n// and post data (if you are using axios) {name, phone, email}\nexport const getUserAction = createAction(\n    user, \n    post, \n    (userId) =\u003e [\n        `user/${userId}`,\n        undefined,\n        {name, phone, email}\n    ]\n)\n\n\n// you can pass multi level functions or promises \n// (args) =\u003e (dispatch, getState) =\u003e (dispatch, getState) =\u003e (dispatch, getState) =\u003e ...\n// calling url 'user/${userId}'\nexport const getUserAction = createAction(\n    user, \n    get, \n    (userId) =\u003e {\n        return (dispatch, getState)=\u003e{\n            return new Promise((resolve) =\u003e {\n                resolve(`user/${userId}`)\n            })\n        }\n    }\n)\n\n// calling url 'user' but replace backend success response to resp.data\nexport const getUserAction = createAction(\n    user, \n    get, \n    'user', \n    (resp) =\u003e {resp.data}\n)\n\n\n// using with multiple entity ids (make two action ids and stores from simgle action)\nexport const getUserByIdAction = createAction(\n    user, \n    get, \n    userId =\u003e `users/${userId}`\n)\n\n\nclass UserComponent extends Component {\n    // ...\n}\n\nconst UserContainer = connect(\n    (state)=\u003e({\n        userData: getActionData(getUserByIdAction.withPrefix(userId))(state, props)\n    }),\n    (dispatch)=\u003e({\n        getUser:(userId) =\u003e (dispatch(getUserByIdAction.withPrefix(userId)(userId))\n    })\n)(UserComponent)\n```\n\n### Additional information, \"createAction\" public methods\n```javascript\n// when you did action, you can use action public methods \nexport const getAllPosts = createAction(\n    postsSchema, \n    get, \n    `posts?_embed=comments\u0026_order=desc` // simple static url\n)\n\n// you can call: \n// getAllPosts.type()\n// getAllPosts.getEntityId(postObj)\n// getAllPosts.getSchema() \n// getAllPosts.clone()\n// getAllPosts.withPrefix(customPrefixId) // customPrefixId might be postId \n// getAllPosts.withName(yourCustomName)\n// getAllPosts.empty()\n\n```\n\nFor details, please look [source](https://github.com/edtoken/redux-tide/blob/master/src/action.js#L348)\n\n### Contributions\nUse [GitHub issues](https://github.com/edtoken/redux-tide/issues) for requests.   \nI actively welcome pull requests; learn how to contribute.   \n\n### Changelog\n[CHANGELOG.md](https://github.com/edtoken/redux-tide/blob/master/CHANGELOG.md).\n\n\n### License\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedtoken%2Fredux-tide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedtoken%2Fredux-tide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedtoken%2Fredux-tide/lists"}