{"id":16593403,"url":"https://github.com/mrozbarry/composable-state","last_synced_at":"2025-04-15T01:35:30.369Z","repository":{"id":57204855,"uuid":"354042454","full_name":"mrozbarry/composable-state","owner":"mrozbarry","description":"A library for composing declarative deep-nested state updates.","archived":false,"fork":false,"pushed_at":"2024-04-05T03:39:27.000Z","size":120,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T13:36:45.597Z","etag":null,"topics":["ecmascript","immutable","library","state-management"],"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/mrozbarry.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2021-04-02T14:26:32.000Z","updated_at":"2024-01-08T21:17:30.000Z","dependencies_parsed_at":"2022-09-26T17:30:33.569Z","dependency_job_id":null,"html_url":"https://github.com/mrozbarry/composable-state","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/mrozbarry%2Fcomposable-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrozbarry%2Fcomposable-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrozbarry%2Fcomposable-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrozbarry%2Fcomposable-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrozbarry","download_url":"https://codeload.github.com/mrozbarry/composable-state/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248989734,"owners_count":21194639,"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":["ecmascript","immutable","library","state-management"],"created_at":"2024-10-11T23:26:32.800Z","updated_at":"2025-04-15T01:35:30.313Z","avatar_url":"https://github.com/mrozbarry.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Composable State\n\nA composable set of tools for deeply updating immutable state objects.\n\n## What's it for?\n\nThe best usage for `composable-state` is when you have large and deeply nested state objects that you are treating as immutable.\nThis could be your application's state, or maybe a json response from an API call.\nWherever you get your data from, if you want to keep the original immutable, use `composable-state` to make new/updated copies.\n\n## Tell me more\n\n - **declarative** - describe how you want to update your state.\n - **simple** - not a lot of methods to learn.\n - **composable** - combine these tiny state update methods to make clean and concise methods.\n\n## Install\n\n```bash\nnpm install --save composable-state\n\n# or\n\nyarn add composable-state\n```\n\n## Usage\n\n```javascript\nimport { composable, selectAll, replace, map } from 'composable-state';\n\nconst myState = {\n  some: { state: { with: { deep: { properties: false } } } },\n  and: { arrays: [1, 2, 3, 4] },\n};\n\nconst immutableCopyOfState = composable(myState, selectAll({\n  'some.state.with.deep.properties': replace(old =\u003e !old),\n  'and.arrays': map(value =\u003e value * 3),\n}));\n/*\nresult:\n\n{\n  some: { state: { with: { deep: { properties: true } } } },\n  and: { arrays: [3, 6, 9, 12] },\n}\n\n*/\n```\n\nAnd for comparison, here's the *worst case* code if you weren't using composable-state:\n\n```javascript\nconst immutableCopyOfState = {\n  ...myState,\n  some: {\n    ...myState.some,\n    with: {\n      ...myState.some.with,\n      deep: {\n        ...myState.some.with.deep,\n        properties: !myState.some.with.deep.properties,\n      },\n    },\n  },\n  and: {\n    ...myState.and,\n    arrays: myState.and.arrays.map(value =\u003e value * 3),\n  },\n}\n```\n\nIf you are 100% rebuilding your state from scratch, maybe you wouldn't need the additional spreads, but that's not the common pattern I've seen or written in my state management-driven applications.\n\n---\n\n## All methods:\n\n - Entry points:\n   - `composable`\n - Actions:\n   - `merge`\n   - `concat`\n   - `setIn`\n   - `replace`\n   - `select`\n   - `selectAll`\n   - `selectArray`\n   - `collect`\n   - `map`\n   - `range`\n\n### composable\n\n```javascript\nimport { composable, replace } from 'composable-state';\n\nconst immutableCopyOfStateWithUpdates = composable(\n  yourState,\n  replace({ hello: 'world' }),\n);\n```\n\nThe composable method is the part that takes in your original state, and returns a new copy of it with the immutable changes you describe in the second parameter.\n\n### replace\n\n```javascript\nimport { composable, replace } from 'composable-state';\n\n// Usage #1 - replace with a completely different value\n\ncomposable('hello', replace('goodbye')); // goodbye\ncomposable(123, replace(false)); // false\n\n// Usage #2 - replace with a callback; use previous value to create new value\n\ncomposable(1, replace(old =\u003e old + 5)); // 6\ncomposable(true, replace(old =\u003e !old)); // false\n```\n\nReplace is meant to replace the value of the current state context.\n\n### merge\n\n```javascript\nimport { composable, merge } from 'composable-state';\n\ncomposable({ foo: 'bar' }, merge({ fizz: 'buzz' })); // { foo: 'bar', fizz: 'buzz' }\n```\n\nMerge will spread a new object into and over the current state context.\nThe object inside `merge` takes prescendence over the original.\n\n### concat\n\n```javascript\nimport { composable, concat } from 'composable-state';\n\ncomposable([1, 2], concat([3, 4])); // [1, 2, 3, 4]\n```\n\nConcat will append the current state context array with the array value inside concat.\n\n### setIn\n\n```javascript\nimport { composable, setIn } from 'composable-state';\n\ncomposable({ hello: 'friend' }, setIn('hello', 'world')); // { hello: 'world' }\ncomposable([12, 13, 99, 15], setIn(2, 14)); // [12, 13, 14, 15]\n```\n\nSetIn lets you update a single child value of the current state context, without changing the context.\nThis is useful for updating single values inside objects or arrays without extra spreads. \nIf you have multiple child updates to do on an object, consider using `merge`.\nIf you have multiple child updates to do on an array, consider using `map` and/or `range`.\n\n### select\n\n```javascript\nimport { composable, select, replace } from 'composable-state';\n\ncomposable({ hello: 'friend' }, select('hello', replace('world'))); // { hello: 'world' }\ncomposable({ { a: [{ b: true }] }, select('a.0.b', replace(old =\u003e !old))); // { a: [{ b: false }] }\n```\n\nSelect allows you to dig into your state to perform updates.\nYou can specify a deep path using `'.'` as a delimiter for properties of your objects, which can look like `foo.bar`.\nFor keys that contain a `.` character, you can wrap it in `[]` brackets, like `foo[key.with.dots]`.\nTechnically, you could just use `[]` brackets, but it doesn't read as nice as the `.` dot delimiter, but that's just a matter of opinion and taste.\nSelecting a deep path creates a state context, and it allows you to perform other immutable operations within the current context.\n\n### selectAll\n\n```javascript\nimport { composable, selectAll, replace } from 'composable-state';\n\nconst initialState = {\n  greeting: 'Hi',\n  user: {\n    name: 'Anonymous',\n  },\n};\n\ncomposable(initialState, selectAll({\n  'greeting': replace('Hello'),\n  'user.name': replace('mrozbarry'),\n})); // { greeting: 'Hello', user: { name: 'mrozbarry' } }\n```\n\nSelectAll works much like select, but gives you the ability to do multiple deep operations on a single state context.\n\n### selectArray\n\n```javascript\nimport { composable, selectAll, replace } from 'composable-state';\n\nconst initialState = {\n  greeting: 'Hi',\n  user: {\n    name: 'Anonymous',\n  },\n};\n\ncomposable(initialState, selectArray(\n  ['user', 'name'],\n  replace('mrozbarry')),\n); // { greeting: 'Hi', user: { name: 'mrozbarry' } }\n```\n\nThe selectArray is what select uses internally, but lets you specify the select path by an array of keys rather than a selector statement.\n\n### collect\n\n```javascript\nimport { composable, collect, replace, concat } from 'composable-state';\n\nconst userState = {\n  picture: null,\n  details: {\n    name: null,\n  },\n};\n\ncomposable(userState, collect([\n  setIn('picture', replace('https://placehold.it/64x64')),\n  select('details.name', replace('mrozbarry')),\n])); // [16, 32]\n```\n\nCollect lets you do multiple immutable updates within the same state context.\n\n### map\n\n```javascript\nimport { composable, map, replace } from 'composable-state';\n\nconst initialState = [{ id: null, name: null }, { id: null, name: null }];\n\ncomposable(initialState, map((user, index) =\u003e collect([\n  setIn('id', Math.ceil(Math.random() * 999)),\n  replace((user) =\u003e merge({ name: `AnonymousUser_${user.id}` })),\n]))); // [{ id: 123, name: 'AnonymousUser_123' }, { id: 2, name: 'AnonymousUser_2' }]\n```\n\nMap allows you to iterate over a state context array, and immutably update each item.\nYou can, of course, chain in more composable-state methods to do even more complex updates.\n\n### range\n\n```javascript\nimport { composable, range } from 'composable-state';\n\n// Can insert items into the middle of an array\ncomposable([1, 2, 5, 6], range(2, 0, replace([3, 4]))); // [1, 2, 3, 4, 5, 6]\n\n// Can remove items from the middle of an array\ncomposable([1, 2, 99, 100, 3, 4], range(2, 2, replace([]))); // [1, 2, 3, 4]\n\n// Can update items\ncomposable([1, 2, 3, 4], range(0, 3, map(value =\u003e value * 5)); // [5, 10, 15, 4]\n```\n\nRange gives you the ability to operate on a subsection of an array.\nYou specify the startIndex, length, and action to run on those items.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrozbarry%2Fcomposable-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrozbarry%2Fcomposable-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrozbarry%2Fcomposable-state/lists"}