{"id":22379834,"url":"https://github.com/cimdalli/redux-ts","last_synced_at":"2025-07-31T01:32:21.311Z","repository":{"id":57351818,"uuid":"66174171","full_name":"cimdalli/redux-ts","owner":"cimdalli","description":"Utils to define react redux reducers/actions in typescript.","archived":false,"fork":false,"pushed_at":"2023-07-11T01:00:42.000Z","size":629,"stargazers_count":17,"open_issues_count":5,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-09T03:18:55.946Z","etag":null,"topics":["action","async","dispatch","fsa","react","reducer","redux","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cimdalli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-08-21T00:27:36.000Z","updated_at":"2023-11-16T09:03:54.000Z","dependencies_parsed_at":"2024-06-19T14:33:52.830Z","dependency_job_id":"d5cf1262-0666-4058-8b42-631a4c8751a3","html_url":"https://github.com/cimdalli/redux-ts","commit_stats":{"total_commits":90,"total_committers":4,"mean_commits":22.5,"dds":"0.33333333333333337","last_synced_commit":"8906ce7c284ea3d035f8f37cec8195bc27b8a32d"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cimdalli%2Fredux-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cimdalli%2Fredux-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cimdalli%2Fredux-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cimdalli%2Fredux-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cimdalli","download_url":"https://codeload.github.com/cimdalli/redux-ts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228204574,"owners_count":17884711,"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":["action","async","dispatch","fsa","react","reducer","redux","typescript"],"created_at":"2024-12-04T23:11:36.230Z","updated_at":"2024-12-04T23:11:36.931Z","avatar_url":"https://github.com/cimdalli.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# redux-ts\r\n\r\nUtils to define react redux reducer/action in typescript.\u003c/p\u003e\r\n\r\n[![build status](https://img.shields.io/travis/cimdalli/redux-ts/master.svg?style=flat-square)](https://travis-ci.org/cimdalli/redux-ts)\r\n[![dependencies Status](https://david-dm.org/cimdalli/redux-ts/status.svg?style=flat-square)](https://david-dm.org/cimdalli/redux-ts)\r\n[![devDependencies Status](https://david-dm.org/cimdalli/redux-ts/dev-status.svg?style=flat-square)](https://david-dm.org/cimdalli/redux-ts?type=dev)\r\n[![npm version](https://img.shields.io/npm/v/redux-ts.svg?style=flat-square)](https://www.npmjs.com/package/redux-ts)\r\n\r\n\u003ch5 align=\"right\"\u003e  Now FSA compliant\u003c/h5\u003e\r\n\r\n\u003e For breaking changes you can take look [CHANGELOG](./CHANGELOG.md)\r\n\r\n## Installation\r\n\r\n```bash\r\n# npm\r\nnpm install --save redux-ts\r\n# yarn\r\nyarn add redux-ts\r\n```\r\n\r\n## Usage\r\n\r\n### Quickstart\r\n\r\nThis is all in one reference implementation of `redux-ts` library. You can enhance that solution depend on your needs.\r\n\r\n```ts\r\nimport { StoreBuilder, ReducerBuilder } from 'redux-ts'\r\n\r\n// Define reducer state\r\ntype LayoutState = { isDark: boolean }\r\n\r\n// Define store state\r\ntype StoreState = { layout: LayoutState }\r\n\r\n// Define action\r\nconst switchTheme = createAction('switchTheme')\r\n\r\n// Build reducer\r\nconst layoutReducer = new ReducerBuilder\u003cLayoutState\u003e()\r\n  .handle(switchTheme, (state, action) =\u003e {\r\n    const isDark = !state.layout.isDark\r\n    return { ...state, isDark }\r\n  },\r\n)\r\n\r\n// Build store\r\nexport const { mapStoreToProps, connected, ...store } = new StoreBuilder\u003cStoreState\u003e()\r\n  .withReducerBuildersMap({ layout: layoutReducer })\r\n  .withDevTools() // enable chrome devtools\r\n  .build()\r\n```\r\n\r\n```tsx\r\nimport React from 'react'\r\nimport { mapDispatchToProps } from 'redux-ts'\r\nimport { connected, mapStoreToProps, store } from './store'\r\n\r\n// Map store to component props\r\nconst storeProps = mapStoreToProps(store =\u003e ({\r\n  theme: store.layout.isDark ? 'dark' : 'light',\r\n}))\r\n\r\n// Pass action object to create dispatchable func. (aka. bindActionCreators)\r\nconst dispatchProps = mapDispatchToProps({ switchTheme })\r\n\r\n// Connect component\r\nconst ConnectedMain = connected(storeProps, dispatchProps)(\r\n  ({ theme, switchTheme }) =\u003e (\r\n    \u003cdiv\u003e\r\n      \u003cspan\u003eCurrent theme: {theme}\u003c/span\u003e\r\n      \u003cbutton onClick={switchTheme}\u003eSwitch theme\u003c/button\u003e\r\n    \u003c/div\u003e\r\n  ))\r\n\r\n// Connect store\r\nconst Root: React.FC = props =\u003e (\r\n  \u003cProvider store={store}\u003e\r\n    \u003cConnectedMain /\u003e\r\n  \u003c/Provider\u003e\r\n)\r\n\r\nReactDOM.render(\u003cRoot /\u003e, document.getElementById('app'))\r\n```\r\n\r\n---\r\n\r\n### Use case (connected-react-router)\r\n\r\nThis is sample usage of `connected-react-router` with `redux-ts`\r\n\r\n```ts\r\nimport { StoreBuilder } from 'redux-ts'\r\nimport { createBrowserHistory } from 'history'\r\nimport { connectRouter, routerMiddleware } from 'connected-react-router'\r\n\r\nexport const history = createBrowserHistory()\r\nconst routerReducer = connectRouter(history)\r\nexport const store = new StoreBuilder\u003cStoreState\u003e()\r\n  .withMiddleware(routerMiddleware(history))\r\n  .withReducer('router', routerReducer)\r\n  .withDevTools() // enable chrome devtools\r\n  .build()\r\n```\r\n\r\n```tsx\r\nimport { Provider } from 'react-redux'\r\nimport { Route, Switch } from 'react-router'\r\nimport { ConnectedRouter } from 'connected-react-router'\r\nimport { history, store } from './store'\r\n\r\nReactDOM.render(\r\n  \u003cProvider store={store}\u003e\r\n    \u003cConnectedRouter history={history}\u003e\r\n      \u003cSwitch\u003e\r\n        \u003cRoute exact path=\"/\" render={() =\u003e \u003cdiv\u003eMatch\u003c/div\u003e} /\u003e\r\n        \u003cRoute render={() =\u003e \u003cdiv\u003eMiss\u003c/div\u003e} /\u003e\r\n      \u003c/Switch\u003e\r\n    \u003c/ConnectedRouter\u003e\r\n  \u003c/Provider\u003e,\r\n  document.getElementById('react-root'),\r\n)\r\n```\r\n\r\n---\r\n\r\n## Using react-ts\r\n\r\n### Store\r\n\r\nCreate redux store with builder pattern.\r\n\r\n```ts\r\nimport { StoreBuilder } from 'redux-ts'\r\nimport { authReducer } from './reducers/authReducer'\r\n\r\nexport const { connected, mapStoreToProps, ...store } = new StoreBuilder\u003cStoreState\u003e()\r\n  .withInitialState({test:true})\r\n  .withMiddleware()\r\n  .withReducer(\"auth\", authReducer)\r\n  .withDevTools()\r\n  .build();\r\n}\r\n```\r\n\r\n- As generic parameter, it requires store state type in order to match given reducers and the state.\r\n- Any number of middleware, enhancer or reducer can be used to build the state.\r\n- `mapStoreToProps` is a dummy method that returns passed parameter again. This method can be used to map store object to props which are consumed from connected components. Return type is `MapStateToPropsParam` which is compatible with [connect](https://react-redux.js.org/api/connect).\r\n- `connected` function is also another dummy function that wraps original [connect](https://react-redux.js.org/api/connect) function but with implicit type resolution support. Problem with original one, when you connect your component with connect method, it is trying to resolve typing by matching signature you passed as parameters to connect _(mapStateToProps, mapDispatchToProps)_ and component own properties. If you are using explicit typing mostly, it is totally fine to use original one. But if you are expecting implicit type resolution, original connect is failing and resolving inner component type as `never`.\r\n\r\n### Actions\r\n\r\nAction declaration can be done with `createAction` function which takes action name as parameter and payload type as generic type.\r\nEach action should have unique identifier which is first parameter of `createAction` function. You can also define your metadata type and pass to generic type as second argument.\r\n\r\n```ts\r\nimport { createAction } from 'redux-ts'\r\n\r\ntype LoginPayload = { username: string; password: string }\r\ntype SetTokenPayload = { token?: string }\r\ntype TokenMeta = { createdAt: Date }\r\n\r\nexport const Login = createAction\u003cLoginPayload\u003e('Login')\r\nexport const Logout = createAction('Logout')\r\nexport const SetToken = createAction\u003cSetTokenPayload, TokenMeta\u003e('SetToken')\r\n```\r\n\r\n- Return value of `createAction` function is [action creator function](https://redux.js.org/basics/actions#action-creators) that takes payload and metadata objects as parameters.\r\n- Return value of [action creator function](https://redux.js.org/basics/actions#action-creators) is plain js object that have _payload_, _meta_ and _type_ fields which is proposed for [FSA](https://github.com/redux-utilities/flux-standard-action).\r\n\r\n### Reducers\r\n\r\nReducers are consumer functions that consumes actions and change application state. Difference from original redux implementation in `redux-ts`, reducers can also dispatch another action asynchronously. Each reducer method should return state value even it doesn't change it. Async dispatch operations will be handled after original dispatch cycle is finished.\r\n\r\n```ts\r\nimport { ReducerBuilder } from 'redux-ts'\r\nimport { Login, Logout, SetToken } from '../actions'\r\nimport { push } from 'connected-react-router'\r\n\r\nconst tokenKey = 'auth'\r\n\r\ntype AuthState = { token?: string }\r\n\r\nexport const authReducer = new ReducerBuilder\u003cAuthState\u003e()\r\n  // Initial value of sub state\r\n  .init({\r\n    token: localStorage.getItem(tokenKey) || undefined,\r\n  })\r\n\r\n  // Handle SetToken action\r\n  .handle(SetToken, (state, action) =\u003e {\r\n    const { token } = action.payload\r\n\r\n    if (token) {\r\n      // If token is valid, persist it on local storage\r\n      localStorage.setItem(tokenKey, token)\r\n    } else {\r\n      // Otherwise remove from local storage\r\n      localStorage.removeItem(tokenKey)\r\n    }\r\n\r\n    return { ...state, token }\r\n  })\r\n\r\n  // Handle Logout action\r\n  .handle(Logout, (state, action, dispatch) =\u003e {\r\n    dispatch(SetToken({ token: undefined })) // First clear token\r\n    dispatch(push('/dashboard')) // Then navigate to home page, it should redirect to login page\r\n\r\n    return state // Return state even there is no change\r\n  })\r\n\r\n  // Handle Login action\r\n  .handle(Login, (state, action, dispatch) =\u003e {\r\n    const { username, password } = action.payload\r\n\r\n    // Request to login\r\n    fetch(`https://server.com/login?u=${username}\u0026p=${password}`)\r\n      .then(x =\u003e x.json())\r\n      .then(data =\u003e {\r\n        // If valid, store token and navigate to home page\r\n        dispatch(SetToken(data.token))\r\n        dispatch(push('/dashboard'))\r\n      })\r\n\r\n    return state\r\n  })\r\n```\r\n\r\n### Connect\r\n\r\n[connect](https://react-redux.js.org/api/connect) method is part of [react-redux](https://github.com/reduxjs/react-redux) library that allows you to connect your react components with redux store.\r\n\r\n\u003e You can use `connected` method for implicit type resolution.\r\n\r\n```tsx\r\nimport * as React from 'react'\r\nimport { mapDispatchToProps } from 'redux-ts'\r\nimport { store } from '../store'\r\nimport { ChangeTheme } from '../actions/layout.actions'\r\nimport { Logout } from '../actions/auth.actions'\r\n\r\nconst { mapStoreToProps, connected } = store\r\n\r\n// Map store object to component props\r\nconst storeProps = mapStoreToProps(store =\u003e ({\r\n  useDarkTheme:!!store.layout.useDarkTheme\r\n}))\r\n\r\n// Map actions to component props\r\nconst dispatchProps = mapDispatchToProps({\r\n  Logout,\r\n  ChangeTheme\r\n})\r\n\r\nexport const Layout = connected(storeProps, dispatchProps)(({\r\n  children,     // standard react prop\r\n  useDarkTheme, // mapped store prop\r\n  Logout,       // mapped action prop\r\n  ChangeTheme   // mapped action prop\r\n  }) =\u003e {\r\n    const appBarRightElement = (\r\n      \u003cdiv\u003e\r\n        \u003cToggle\r\n          onToggle={ChangeTheme}\r\n          label={useDarkTheme: 'dark' : 'light'}\r\n          toggled={useDarkTheme}\r\n        /\u003e\r\n        \u003cFlatButton onClick={Logout} label=\"logout\" /\u003e\r\n      \u003c/div\u003e\r\n    )\r\n\r\n    return (\r\n      \u003cdiv\u003e\r\n        \u003cAppBar iconElementRight={appBarRightElement}/\u003e\r\n        {children}\r\n      \u003c/div\u003e\r\n    )\r\n  })\r\n```\r\n\r\n## Example\r\n\r\n[react-material-demo](https://github.com/cimdalli/react-material-demo) (Not up to date)\r\n\r\n## License\r\n\r\nMIT\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcimdalli%2Fredux-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcimdalli%2Fredux-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcimdalli%2Fredux-ts/lists"}