{"id":13499721,"url":"https://github.com/mquan/cortex","last_synced_at":"2025-05-16T01:05:02.628Z","repository":{"id":13505575,"uuid":"16196446","full_name":"mquan/cortex","owner":"mquan","description":"An immutable data store for managing deeply nested structure with React","archived":false,"fork":false,"pushed_at":"2018-02-20T10:20:21.000Z","size":412,"stargazers_count":1057,"open_issues_count":2,"forks_count":49,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-05-08T13:15:31.064Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mquan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2014-01-24T05:57:09.000Z","updated_at":"2025-03-04T10:54:11.000Z","dependencies_parsed_at":"2022-09-06T06:41:59.968Z","dependency_job_id":null,"html_url":"https://github.com/mquan/cortex","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mquan%2Fcortex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mquan%2Fcortex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mquan%2Fcortex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mquan%2Fcortex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mquan","download_url":"https://codeload.github.com/mquan/cortex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254425796,"owners_count":22069232,"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-31T22:00:39.400Z","updated_at":"2025-05-16T01:05:02.581Z","avatar_url":"https://github.com/mquan.png","language":"JavaScript","readme":"Cortex is an immutable data store for managing deeply nested structure with React\n\n**Key features:**\n- supports deeply nested data\n- uses immutable data, which allows fast comparison in `shouldComponentUpdate`\n- very efficient batch updates\n- simple APIs with built-in methods for working with arrays and hashes\n- very lightweight (4.5kB minified and gzip)\n- written in ES6\n\n**Demos**\n\n[skyline (4-level nested components)](https://mquan.github.io/cortex/examples/skyline/)\n\n[file system (arbitrarily deep structure of a single component type)](https://mquan.github.io/cortex/examples/file_system/)\n\n# Quickstart\nInitialize a cortex object\n```javascript\nvar data = {a: 100, b: [1, 2, 3]};\n\nvar cortex = new Cortex(data, function(updatedCortex) {\n  //trigger React component to update props\n  myComponent.setProps({cortex: updatedCortex});\n});\n```\n\nGet a nested cortex object\n```javascript\ncortex.a\n\n//Also works\ncortex['a']\n```\n\nGet a nested cortex element in an array\n```javascript\ncortex.b[0]\n```\n\nGet the actual value\n```javascript\ncortex.a.getValue()\n// ==\u003e 100\n```\n\nChange data\n```javascript\ncortex.a.set(200);\ncortex.a.getValue();\n// ==\u003e 200\n```\n\nChange data from root object\n```javascript\ncortex.set({a: 300})\ncortex.getValue()\n// ==\u003e {a: 300}\n```\n* Note that new value is only available after `onUpdate` callback is run.\n\nAdd callbacks\n```javascript\ncortex.onUpdate(myCallback);\n```\n\n### ES6 Guide\nSince React 0.13.0 removed `setProps` for ES6 React.Component class you have to define your cortex data as state instead\n\n```javascript\nclass MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // Assume you pass your data into props as myData\n    var myCortex = new Cortex(props.myData, (updatedCortex) =\u003e {\n      this.setState({myCortex: updatedCortex});\n    });\n\n    this.state = {myCortex: myCortex};\n  }\n\n  render() {\n    // access cortex data from this.state.myCortex\n  }\n}\n```\n\n# Cortex 2.0 migration guide\n\nThe biggest change in v2 is immutable data. This allows us to implement `shouldComponentUpdate` as easy as\n\n```javascript\nshouldComponentUpdate: function(nextProps, nextState) {\n  return nextProps.myCortex !== this.props.myCortex;\n}\n```\n\nImmutability also allows us to remove `getChanges` and `didChange` methods.\n\nBREAKING CHANGES\n- `on('update', callback)` is now simply onUpdate(callback)\n- `off('update', callback)` is removed\n- `insertAt` and `removeAt` are replaced by `splice`, which behaves the same way as `Array.prototype.splice`\n- `add` is replaced by `merge`\n- for aesthetic reason, `remove` and `destroy` are swapped. So you would call `remove(key)` to remove a nested child and call `destroy` to remove self.\n\n# Overview\n\nCortex's main objective is to support arbitrarily deep data structure. Similar to the Flux pattern, it forces data to flow in one direction such that when an update occurs data is updated in one place, at the root, and allowed to propagate down the chain. Unlike Flux, however, Cortex does not require separate Action, Dispatcher, and Store entities.\n\nCortex is simply a store that works for updates at any level. It achieves this by utilizing cursors, which lets each nested node keep track of its path as accessed from the root. When an update occurs, the affected node emits an update event whose payload contains the path of the node as well as instructions on how to update the data at the root. Cortex's internal PubSub picks up the event and routes it to the affected root node. From there, every affected node is rewrapped and updated to maintain immutability while leaving all unaffected nodes untouched. This allows for extremely simple and efficient `shouldComponentUpdate` implementation.\n\nCortex allows components to manage their own data instead of defining global update ACTIONS. A deeply nested data structure can simply be passed into the parent Component, which then passes pieces of the data onto child components without worrying about how they are used.\n\nCortex also provides a few optimizations to help boost performance. First, Cortex will skip triggering React rerender when an update results in no actual data change. Secondly, Cortex batches all updates in a cycle into one call so that React is only triggered to render once. This is especially useful when updating multiple data nodes, such as data in an array.\n\n# Basic example\n\nThe following example has two components, Order and Item. An Order contains an array of Items, and each Item can increase its own quantity attribute.\n\n```javascript\nvar Item = React.createClass({\n  shouldComponentUpdate: function(nextProps, nextState) {\n    nextProps.item !== this.props.item;\n  },\n  increase: function() {\n    var quantity = this.props.item.quantity.getValue();\n    this.props.item.quantity.set(quantity + 1);\n  },\n  subTotal: function() {\n    return this.props.item.quantity.getValue() * this.props.item.price.getValue();\n  },\n  render: function() {\n    return(\n      \u003cdiv className=\"item\"\u003e\n        \u003ca href=\"#\" onClick={this.increase}\u003e+\u003c/a\u003e\n        \u003cspan\u003e{this.props.item.quantity.getValue()}\u003c/span\u003e\n        \u003cspan\u003e{this.props.item.name.getValue()}\u003c/span\u003e\n        \u003cspan\u003e${this.subTotal()}\u003c/span\u003e\n      \u003c/div\u003e\n    );\n  }\n});\n\nvar Order = React.createClass({\n  shouldComponentUpdate: function(nextProps, nextState) {\n    return nextProps.order !== this.props.order;\n  },\n  render: function() {\n    var items = this.props.order.map(function(item){\n      return \u003cItem item={item} /\u003e;\n    });\n    return(\n      \u003cdiv\u003e{items}\u003c/div\u003e\n    );\n  }\n});\n\nvar orderData = [\n      {name: \"Burger\", quantity: 2, price: 5.0},\n      {name: \"Salad\", quantity: 1, price: 4.50},\n      {name: \"Coke\", quantity: 3, price: 1.50}\n    ];\n\n//Initialize cortex with data and pass in a callback to run when data is updated.\nvar orderCortex = new Cortex(orderData);\n\nvar orderComponent = React.renderComponent(\n  \u003cOrder order={orderCortex} /\u003e, document.getElementById(\"order\")\n);\n\norderCortex.onUpdate(function(updatedOrder) {\n  orderComponent.setProps({order: updatedOrder});\n});\n```\n\nFirst we initialize cortex with:\n```javascript\nvar orderCortex = new Cortex(orderData);\n```\n\nThen it's passed into the Order component to render the Item components.\n\nWe set a callback to run on update event using\n```\norderCortex.onUpdate(function(updatedOrder) {\n  orderComponent.setProps({order: updatedOrder});\n});\n```\n\nIn Item component, note that we display the quantity value with ``this.props.item.quantity.getValue()``. This is because ``this.props.item.quantity`` only gives us the wrapper of the ``quantity`` attribute, we need to call ``getValue()`` to get the actual value.\n\nIn `increase` method, we use ``this.props.item.quantity.set(quantity + 1)`` to add 1 to the current quantity value.\n\nNote that we implement `shouldComponentUpdate` by simply comparing the current and next props. This comparison is extremely fast since cortex returns a brand new immutable wrapper when data change.\n\n# Cortex API\n\n### Initialize:\n\n```javascript\nnew Cortex(data, function() {\n  //trigger your React component to update\n});\n```\n\n### Instance methods:\n\n    Method                    | Description\n    --------------------------|:-------------------\n    `getValue()`              | Returns the actual value\n    `val()`                   | Alias for `getValue`\n    `set(value)`              | Changes the value and rewrap the subtree.\n    `destroy()`               | Self destruct method: remove self from parent if nested, set value to undefined if root level.\n    `onUpdate(callback)`      | Add a callback to run on update event (only available on root object)\n\n### Cortex wrapper of array data has the following methods:\n\n    Method                         | Description\n    -------------------------------|:----------------------\n    `count()`                      | Returns length of nested wrappers\n    `forEach(callback)`            | Iterates over all elements. The callback accepts the following input `(wrapperElement, index, wrapperArray)`\n    `map(callback)`                | Returns a new array as returned by the callback. Callback accepts same input as forEach callback\n    `filter(callback, thisArg)`    | Returns a new array of wrappers whose elements satisfy condition return by callback.\n    `find(callback)`               | Returns the first wrapper element that meets the condition returned by callback. Callback accepts same input as forEach callback.\n    `findIndex(callback)`          | Returns index of first wrapper element that meets condition returned callback. Callback accepts same input as forEach callback.\n    `push(value)`                  | Inserts and rewrap the value at the end of the array.\n    `pop()`                        | Removes the last element in the array\n    `unshift(value)`               | Inserts and rewrap the value at the front of the array.\n    `shift()`                      | Removes the first element in the array\n    `splice(index, removeCount, element1 [, element2, ...])`     | Remove `removeCount` from the array and insert elements into the array. This is similar to the native `Array.prototype.splice` method\n\n### Cortex wrapper of hash data has the following methods:\n    Methods                        | Description\n    -------------------------------|:------------------------\n    `keys()`                       | Returns the array of keys\n    `values()`                     | Returns the array of values\n    `hasKey(key)`                  | Returns boolean value whether the key exists\n    `forEach(callback)`            | Iterates over every key and value pair. The callback accepts the following inputs `(key, wrapperElement)`\n    `remove(key)`                 | Removes the specified key and value pair\n    `merge({key1: value1[, key2: value2, ...]})`              | Adds/modifies the specified key and value pairs\n\n# CDN\n\n[cortex.2.0.2.js](https://cdn.rawgit.com/mquan/cortex/dc1fc8cf795828d24c2740ee48d10ecf0768320e/build/cortex.js)\n[cortex.2.0.2.min.js](https://cdn.rawgit.com/mquan/cortex/dc1fc8cf795828d24c2740ee48d10ecf0768320e/build/cortex.min.js)\n\n\n# Install via Bower\n```console\nbower install cortexjs\n```\n\nReference the js file\n```html\n\u003cscript src=\"/bower_components/cortexjs/build/cortex.js\"\u003e\u003c/script\u003e\n```\n\n# Using cortex with node.js\nInstall via npm\n```console\nnpm install cortexjs\n```\n\nUse it:\n```javascript\nCortex = require(\"cortexjs\");\n\nwrappedData = new Cortex({mydata: 1});\nconsole.log(wrappedData.mydata.getValue()); //1\n\nwrappedData.mydata.set(100);\nconsole.log(wrappedData.mydata.getValue()); //100\n```\n\n# Building Cortex\nTo build Cortex:\n```console\ngulp\n```\n\nTo run test:\n```console\ngulp test\n```\n\nTo compile jsx files in examples into js files:\n```console\ngulp react\n```\n\n# Design \u0026 Optimizations\n\nBesides providing the convenience of allowing you to update data from any level, Cortex also has several optimizations that help boost performance.\n\n### 1. Deep comparison between old and new values\n\nWhen you issue a `set(newValue)` call, no data actually changes at that point. What happens internally is the wrapper being called publishes a notification to the master cortex wrapper passing along a payload consisting of the path for locating the data and the new value (Yes, there is a pub/sub system within Cortex.) The root wrapper then performs a deep comparison between the old and new data to determine whether it should trigger the update action. If no change was made, the process just terminates without touching the data nor invoking the callbacks.\n\nDeep comparison may sound costly but in practice when you call `set(newValue)` the newValue usually isn't deeply nested (if it is and the actual change is many layers deep then you should consider calling `set(newValue)` on the wrapper at the level that the change actually occurs.) In some situations where you have to pass in arbirarily deeply nested value the comparison work is still worth it because it can potentially save you from unnecessarily rewrapping and triggering React to update.\n\n\n### 2. Batch rewrap and invoke callbacks once for multiple updates\n\nWhen multiple updates occur at the same time, it will result in the same number of data rewrapping and callback invocations, which usually involve triggering React to update that same number of times. While React has good diffing algorithm for efficiently redrawing the DOM it would be even more efficient if React doesn't have to perform DOM comparison mutliple times. Cortex avoids triggering React by running callback only once for updates happening at the same time. This is especially useful when an array is being updated one element at a time or a deeply nested piece of data change at multiple levels.\n","funding_links":[],"categories":["Awesome React","JavaScript","Uncategorized"],"sub_categories":["Tools","Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmquan%2Fcortex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmquan%2Fcortex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmquan%2Fcortex/lists"}