{"id":13394721,"url":"https://github.com/tonyhb/redux-ui","last_synced_at":"2025-03-13T20:31:39.453Z","repository":{"id":57160664,"uuid":"47586368","full_name":"tonyhb/redux-ui","owner":"tonyhb","description":"Easy UI state management for react redux","archived":true,"fork":false,"pushed_at":"2019-02-19T08:19:15.000Z","size":94,"stargazers_count":635,"open_issues_count":38,"forks_count":58,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-03-05T10:01:54.407Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/tonyhb.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}},"created_at":"2015-12-07T23:42:37.000Z","updated_at":"2025-01-16T05:52:15.000Z","dependencies_parsed_at":"2022-09-09T07:31:06.457Z","dependency_job_id":null,"html_url":"https://github.com/tonyhb/redux-ui","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyhb%2Fredux-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyhb%2Fredux-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyhb%2Fredux-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonyhb%2Fredux-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonyhb","download_url":"https://codeload.github.com/tonyhb/redux-ui/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243196653,"owners_count":20251861,"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":[],"created_at":"2024-07-30T17:01:29.348Z","updated_at":"2025-03-13T20:31:38.937Z","avatar_url":"https://github.com/tonyhb.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Uncategorized","Code Design","React Integration"],"sub_categories":["Uncategorized","Data Store","Component State"],"readme":"[![Circle CI](https://circleci.com/gh/tonyhb/redux-ui.svg?style=svg)](https://circleci.com/gh/tonyhb/redux-ui)\n\n# redux-ui: ui state without profanity\n\nThink of redux-ui as **block-level scoping** for UI state. In this example each block-scope represents a component, and each variable represents a UI state key:\n\n```js\n{\n  // Everything inside this scope has access to filter and tags. This is our root UI component.\n  let filter = '';\n  let tags = [];\n\n  // Imagine the following scopes are a list of to-do tasks:\n  {\n    // Everything inside this scope has access to isSelected - plus all parent variables.\n    let isSelected = true\n  }\n  {\n    // This also has isSelected inside its scope and access to parent variables, but\n    // isSelected is in a separate scope and can be manipulated independently from other\n    // siblings.\n    let isSelected = false\n  }\n}\n```\n\nWrap your root component with the redux-ui `@ui()` decorator.  It's given a new scope for temporary UI variables which:\n\n- are automatically bound to `this.props.ui`\n- are automatically passed any child component wrapped with the `@ui()` decorator\n- will be automatically reset on componentWillUnmount (preventable via options)\n- can be reset manually via a prop\n- are updatable by any child component within the `@ui()` decorator\n\nThis is **powerful**. **Each component is reusable** and can still affect UI state for parent components.\n\n### Setup\n\n**Step 1**: Add the redux-ui reducer to your reducers **under the `ui` key**:\n```js\nimport { reducer as uiReducer } from 'redux-ui'\n// ...\ncombineReducers({ ...yourReducers, ui: uiReducer })\n```\n\n**Step 2**: In each 'scene' or parent component add the UI decorator with the key in\nwhich to save all state:\n```\nimport ui from 'redux-ui';\n\n@ui({\n  state: {\n    yourVars: 'withDefaults',\n    filters: []\n  }\n})\nclass YourComponent extends React.Component {\n}\n```\n\n**Step 3**: In each child component use the basic `@ui()` decorator; it will\nautomatically read and write UI state to the parent component's UI key.\n\nYou can also define variables in child components. If your child component has\nvariables named the same as a parent component think of block scoping:\neverything within your child component down will read from the child's scope,\nbut the parent will use the parent's UI variable.\n\n### Usage\n\nThe `@ui` decorator injects four props into your components:\n\n1. `uiKey`: The key passed to the decorator from the decorator (eg.\n   'some-decorator' with `@ui('some-decorator')`\n2. `ui`: The UI state for the component's `uiKey`\n3. `updateUI`: A function accepting either a name/value pair or object which\n   updates state within `uiKey`\n4. `resetUI`: A function which resets the state within `uiKey` to its default\n\nThe decorator will set any default state specified (see below).\nOn `componentWillUnmount` the entire state in `uiKey` will be set to undefined.\nYou can also blow away state by calling\t`resetUI` (for example, on router\nchanges).\n\n### Decorator API\n\nThe decorator takes an object of options:\n\n```js\n@ui({\n  // optional key which is used to determine the UI path in which state will\n  // be stored. if omitted this is randomly generated.\n  key: 'some-name',\n  // optional persist, defaults to false. if set to true persist will keep UI\n  // state for this component after it unmounts. if set to false the UI state will\n  // be deleted and recreated when the component remounts\n  persist: true,\n  // **required**: UI state for the component\n  state: {\n    uiVar1: '',\n    // You can set default UI state based on the component's props and the\n    // global store's state.\n    uiVar2: (props, state) =\u003e state.router.location.query.searchTerm\n  },\n  // customReducer: you can handle the UI state for this component's scope by dispatching actions\n  reducer: (state, action) =\u003e {\n    // state represents *only* the UI state for this component's scope - not any children\n    switch(action.type) {\n      case '@@reduxReactRouter/routerDidChange':\n        if (action.payload.location.query.extra_filters) {\n          return state.set('extraFilters', true);\n        }\n      }\n      return state;\n    }\n  },\n  // optional mergeProps passed to react-redux' @connect\n  mergeProps: () =\u003e ({}),\n  // optional `options` passed to react-redux @connect\n  options: {}\n})\n```\n\n### Non-decorator API\n\nYou can use redux-ui without using an ES7 decorator like so:\n\n```\nimport ui from 'redux-ui';\n// or ui = require('redux-ui').default;\n\nclass SomeComponent extends Component {\n}\n\nSomeComponentWithUI = ui({ key: 'some-name', state: { ... }})(SomeComponent);\n```\n\n##### `key`: string, defaults to random characters\n\nThe name of the key used in the UI reducer under which we store all state.  Allows you to create selectors in reselect with known paths, and allows setting `persist` below.\n\nIf this is not specified it will be autogenerated based on the component name suffixed with a random hex code.  Components using the same key will share the same UI context, so don't supply a name to a list of components (generated in a loop) if they need their own UI state.\n\n\n##### `persist`: bool, defaults to `false`\n\nSet to true if the UI state for this component should persist after `componentWillUnmount`.  You must also explicitly define a `key` for this component, otherwise the component will randomize the key and load new UI state on instantiation.\n\n**Note**: All parent UI components also need to set this to true for this to take effect. Think of block-level scoping again — if a parent scope quits all child scopes are also out of context.\n\n##### `state`: object\n\nAll UI variables need to be explicitly defined in the state object.  This allows us to determine which scope a variable belongs to, as scope is inherited in the component tree.  Think of this as using `let` inside your block scopes.\n\n### Examples\n\n```js\nimport ui from 'redux-ui';\n\n// Component A gets its own context with the default UI state below.\n// `this.props.ui` will contain this state map.\n@ui({\n  state: {\n    // use the filter query parma via redux-router as the default\n    filter: (props, state) =\u003e state.router.location.query.filter,\n    isFormVisible: true,\n    isBackgroundRed: false\n  }\n})\nclass A extends Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        // This will render '{ \"filter\": '', isFormVisible: true, isBackgroundRed: false }'\n        \u003cpre\u003e\u003ccode\u003e{ this.props.ui }\u003c/code\u003e\u003c/pre\u003e\n\n        // Render child B\n        \u003cB /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\n// B inherits context from A and adds its own context.\n// This means that this.props.ui still contains A's state map.\n@ui()\nclass B extends Component {\n  render() {\n    return \u003cC /\u003e;\n  }\n}\n\n// C inherits context from its parent B. This works recursively,\n// therefore C's `this.props.ui` has the state map from `A` **plus**\n// `someChildProp`.\n//\n// Setting variables within C updates within the context of A; all UI\n// components connected to this UI key will receive the new props.\n@ui({\n  state: {\n    someChildProp: 'foo'\n  }\n})\nclass C extends Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cp\u003eI have my own UI state C and inherit UI state from B and A\u003c/p\u003e\n        \u003cp\u003eIf I define variables which collide with B or A mine will\n        be used, as it is the most specific context.\u003c/p\u003e\n    );\n  }\n}\n```\n\n### Aims\n\nUI state:\n\n1. Should be global\n2. Should be easily managed from each component via an action\n3. Should be easy to reset (manually and when unmounting)\n\nAll of these goals should be easy to achieve.\n\n---\n\nMIT license.\n\nWritten by [Franklin Ta](https://github.com/fta2012) and [Tony Holdstock-Brown](https://github.com/tonyhb).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonyhb%2Fredux-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonyhb%2Fredux-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonyhb%2Fredux-ui/lists"}