{"id":17362361,"url":"https://github.com/isocroft/kahtox","last_synced_at":"2026-04-30T00:32:39.137Z","repository":{"id":73653716,"uuid":"222422012","full_name":"isocroft/kahtox","owner":"isocroft","description":"A small JavaScript library based on state machines (but not entirely) for isolating and managing its own UI-state (transient / non-persisted) layer while controlling a Domain-state (non-transient / persisted) layer like Redux, Radixx","archived":false,"fork":false,"pushed_at":"2019-11-20T18:11:32.000Z","size":161,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T17:29:21.700Z","etag":null,"topics":["circular-directed-ui-state-graphs","domain-state-management","gating","radixx","redux","reusable-state-graph","state-graphs","sub-state-graphs","transition-change-dispatchguards","transition-hooks","ui-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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/isocroft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-11-18T10:26:37.000Z","updated_at":"2019-11-21T10:06:49.000Z","dependencies_parsed_at":"2023-09-02T19:37:31.029Z","dependency_job_id":null,"html_url":"https://github.com/isocroft/kahtox","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/isocroft/kahtox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fkahtox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fkahtox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fkahtox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fkahtox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/isocroft","download_url":"https://codeload.github.com/isocroft/kahtox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fkahtox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32449810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"ssl_error","status_checked_at":"2026-04-29T22:10:49.234Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["circular-directed-ui-state-graphs","domain-state-management","gating","radixx","redux","reusable-state-graph","state-graphs","sub-state-graphs","transition-change-dispatchguards","transition-hooks","ui-state-management"],"created_at":"2024-10-15T19:37:59.289Z","updated_at":"2026-04-30T00:32:39.118Z","avatar_url":"https://github.com/isocroft.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kahtox\nA small JavaScript library based on state machines (but not entirely) for isolating and managing its own UI-state (transient / non-persisted) layer while controlling a Domain-state (non-transient / persisted) layer like Redux, Radixx.\n\n## Inspiration\n\nThis work was inspired by earlier works by [David K. Piano](https://twitter.com/davidkpiano) on [**xstate**](https://github.com/davidkpiano/xstate) and [Krasimir Tsonev](http://krasimirtsonev.com/) on [**stent**](https://github.com/krasimir/stent). Drawing from their work and thinking deeply about the problem, it was now clear that there needed to be a paradigm shift in the way we approached state management. *Xstate* for me is neat and helpful but is also too verbose and seeks to replace *Redux*. I have all these lingua and terminilogy like (Sequence, History, Orthogonal, Events, States, Effects, Guard) that i have to learn to make sense of it all. *Stent* on the other hand is less verbose but however also seeks to replace *Redux* completely adds composability problems (how do you compose one or more state machines together ?) to its setup process.\n\nNow, *Kahtox* still makes use of finite state machine graphs and transitions too but doesn't try to replace libraires like *Redux* or *Radixx* but seeks to work along side these libraires. The reason for this is that UI state is NEVER meant to be stored in any state container because it is TRANSIENT and is totally irreleveant after a record of its occurence. UI state doesn't also need to be tracked for changes. Only Domain state needs to be stored in a state container and tracked for changes over time.\n\nBy seperating both state types into their respective layers and having one (UI state) control the other (Domain state), it becomes easier to manage outcomes and enforce guards for user interface interactions.\n\n## Motivation\n\nThe need to separate today's monolith state management layer architecure into a more managable set of layers (2 of them actually)\n\n1. UI State Layer (Transient / Non-Persisted)\n2. Domain State Layer (Non-Transient / Persisted)\n\nThis need is long ovedue. state management libraries like Redux were under too much pressure to manage both UI State (which actually doesn't need to be persisted) and Domain state (which is actually meant to be put into a state container and persisted across web sessions)\n\nHowever, this hasn't happened because everything is still built around verbosity and replacement. The idea is not to seek to replace Redux (even though [redux could be better redesigned](https://hackernoon.com/redesigning-redux-b2baee8b8a38)) but to reduce the amount of \"useless work\" that libraries like *Redux* are doing - which is tracking UI State in the store state container.\n\n## Concepts\n\n\u003eReusability of State Graphs \u0026 Sub State Graphs\n\nWhen state graphs are created, it should be created with reusability in mind. For instance, a state graph can be reduced into 2 or more sub state graphs.\n\n\u003eState Graphs Should NEVER Depict Side-Effects Or Its Causality\n\nWhenever your application goes into an error-state (Form Validation Errors, HTTP Errors), state graphs should NEVER ever deal with these situation. It should also never depict it as a state.\n\n\u003eIsolating Domain State Containers From Unecessary View Layer (UI) Update Info Storage\n\nUI state is never meant to be tracked or persisted in a state container. UI state is only useful immediately before it causes a view re-render or view layer update. After that, it is no longer useful.\n\n\u003eUI State Changes Should Trigger View Layer (UI) Updates / Re-renders\n\nUI state changes (driven by state graphs) will mostly trigger view updates except when the state is going back to the `$initial` state. In other words, state graphs MUST always be unidirectional (directed) circular graphs which circle back to the `$initial` state.\n\n## Architecture\n\nIn time past, when building web apps, we made use of *Redux* or *Radixx* as both a **UI State Layer library** and a **Domain State Layer library**. This unfortunately created a lot of jank with the *redux* state tree and made it difficult to use the **UI State** the way it ought to be used. However, these days, we separate the 2 **State Layers** making way for *Redux* or *Radixx* to only be used for the **Domain State Layer** and *Kahtox* to be used for the **UI State Layer**. See below:\n\n| BEFORE                    | NOW\t                |\n| ------------------------- | ------------------------- |\n| \u003cimg src=\"before.png\" /\u003e  | \u003cimg src=\"now.png\" /\u003e     |\n\nEven the **Redux Docs** had [this](https://redux.js.org/faq/organizing-state#do-i-have-to-put-all-my-state-into-redux-should-i-ever-use-reacts-setstate) to say about how to organize state. I believe it is crucial to separate your **UI State** from your **Domain State** as it is not useful in anyway to bring both state into *Redux*. Another point to this is how separating the **UI State** and **Domain State** makes it easy for you to [co-locate](https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster) your **UI State** into the component where it is needed the most and have better access to the **Domain State**.\n\nHere is what *Kent .C. Dodds* had to say on the issue of **co-location** of state:\n\n\u003eWhere I see this principle apply in real-world applications is when people put things into a global Redux store or in a global context that don't really need to be global.\n\n## State Graphs (Diagrams)\n\nState graphs are just like state machines when depicted visually with diagrams but have a few differences.\n\n## Getting Started\n\n\u003e filename: Button.js\n```js\nimport React, { Component } from 'react';\n\nclass Button extends Component {\n\tconstructor(props){\n\t\tsuper(props);\n\t}\n\t\n\trender(){\n\t\tconst type = this.props.type || 'button';\n\t\tconst text = this.props.text;\n\t\tconst disabled = this.props.disabled;\n\t\tconst onButtonClick = this.props.onButtonClick || () =\u003e true;\n\t\treturn (\n\t\t\tdisabled === true \n\t\t\t? \u003cbutton type={type} disabled=\"disabled\" onClick={onButtonClick}\u003e{text}\u003c/button\u003e\n\t\t\t: \u003cbutton type={type} onClick={onButtonClick}\u003e{text}\u003c/button\u003e\n\t\t);\n\t}\n}\n\nexport default Button;\n```\n\n\u003e filename: Input.js\n```js\nimport React, { Component } from 'react';\n\nclass Input extends Component {\n\tconstructor(props){\n\t\tsuper(props);\n\t}\n\t\n\trender(){\n\t\tconst type = this.props.type || 'text';\n\t\tconst value = this.props.value || '';\n\t\tconst status = this.props.status || 'pristine';\n\t\tconst pattern = this.props.pattern || '[^\\\\S\\\\f\\\\t\\\\b\\\\n\\\\r]+';\n\t\tconst name = this.props.name;\n\t\t\n\t\tconst onInputChange = this.props.onInputChange || (e) =\u003e true;\n\t\tconst onInputKeyDown = this.props.onInputKeyDown || (e) =\u003e true;\n\t\t\n\t\treturn (\n\t\t\t(type === 'checkbox' || type === 'radio' || type === 'hidden')\n\t\t\t? \u003cinput type={type} data-status={status} name={name} value={value} onChange={onInputChange} /\u003e\n\t\t\t: \u003cinput type={type} data-status={status} data-pattern={pattern} name={name} value={value} onChange={onInputChange} onKeyDown={onInputKeyDown} /\u003e\n\t\t);\n\t}\n}\n\nexport default Input;\n```\n\n\u003e filename: FormBox.js\n```js\nimport React, { Component, Children, cloneElement } from 'react';\nimport kahtox from 'kahtox';\n\nconst validateFormGuard = function ({ payload }){\n\treturn payload.reduce(\n\t\t(val, acc) =\u003e (\n\t\t\tval.text.length !== 0 \u0026\u0026 val.status === 'pristine' \u0026\u0026 acc\n\t\t), \n\t\ttrue\n\t)\n};\n\nlet stateGraph = {\n\t$initial: 'empty',\n\tstates: {\n\t\t'empty': {\n\t\t\t'input-keys': {\n\t\t\t\tnextState: 'filling',\n\t\t\t\taction: null\n\t\t\t\t/* notifyView: false */\n\t\t\t}\n\t\t},\n\t\t'filling': {\n\t\t\t'input-keys': {\n\t\t\t\tnextState: 'filling',\n\t\t\t\taction: null\n\t\t\t},\n\t\t\t'input-change': {\n\t\t\t\tnextState: 'filling',\n\t\t\t\taction: null\n\t\t\t},\n\t\t\t'mouse-over-button': {\n\t\t\t\tnextState: 'filled',\n\t\t\t\taction: null,\n\t\t\t\tguard: validateFormGuard\n\t\t\t},\n\t\t\t'tab-into-button': {\n\t\t\t\tnextState: 'filled',\n\t\t\t\taction: null,\n\t\t\t\tguard: validateFormGuard\n\t\t\t}\n\t\t},\n\t\t'filled': {\n\t\t\t'button-click': {\n\t\t\t\tnextState: 'empty',\n\t\t\t\taction: null\n\t\t\t}\n\t\t}\n\t}\n};\n\nconst getUTF8StringSize = (str) =\u003e {\n\tlet sizeInBytes = str.split('')\n\t    .map(function( ch ) {\n\t      return ch.charCodeAt(0);\n\t    }).map(function( uchar ) {\n\t      // The reason for this is explained later in\n\t      // the section “An Aside on Text Encodings”\n\t      return uchar \u003c 128 ? 1 : 2;\n\t    }).reduce(function( curr, next ) {\n\t      return curr + next;\n\t    }, 0);   \n  \treturn sizeInBytes;\n};\n\nconst debounce = (func, wait, immediate) =\u003e {\n\tlet timeout;\n\treturn function() {\n\t\tlet context = this;\n\t\tlet args = arguments;\n\t\tthis later = function() {\n\t\t\ttimeout = null;\n\t\t\tif (!immediate) func.apply(context, args);\n\t\t};\n\t\tlet callNow = immediate \u0026\u0026 !timeout;\n\t\tclearTimeout(timeout);\n\t\ttimeout = setTimeout(later, wait);\n\t\tif (callNow) func.apply(context, args);\n\t};\n};\n\nclass FormBox extends Component {\n   constructor(prop){\n\tsuper(props);\n\tthis.grapher = kahtox.makeGrapher(stateGraph)\n\t\n\tlet controls = {};\n\t\n\tthis.props.children.forEach(function looper(child){\n\t\tcontrols[child.name] = { text: child.value, status: child.status }; // status:'pristine'\n\t});\n\t\n\tthis.state = {\n\t\tmode: this.grapher.initial,\n\t\tparentMode: this.props.mode,\n\t\tcontrols: controls,\n\t\telements: this.props.children\n\t}\n\t\n\tthis.grapher.afterTransition((mode, data, isError) =\u003e {\n\t\tlet updatePatch = { mode };\n\t\t\n\t\tif(data !== null \n\t\t\t\u0026\u0026 !isError){\n\t\t\tupdatePatch.controls = data;\n\t\t}\n\t\t\n\t\tthis.setState(\n\t\t\tprevState =\u003e Object.assign(prevState, updatePatch)\n\t\t)\n\t})\n   }\n   \n   componentDidMount(){\n   \t;\n   }\n   \n   onInputKeys(e){\n   \tlet data = this.state.controls;\n\tlet regexp = null;\n\n\tdata[e.target.name].text = e.target.value;\n\tdata[e.target.name].status =  'dirty';\n\n\tif(e.target.type === 'checkbox'){\n\t\tdata[e.target.name].status = 'pristine';\n\t}\n\n\tif(e.target.type === 'text'){\n\t\tregexp = new RegExp(e.target.getAttribute('data-pattern'));\n\n\t\tif(!regexp.test(data[e.target.name].text)){\n\t\t\tdata[e.target.name].status = 'error';\n\t\t}\n\t}\n\n\tthis.grapher.dispatch('input-keys', data);\n   }\n   \n   onInputChange(e){\n   \tlet data = this.state.controls;\n\tdata[e.target.name].text = e.target.value;\n\tdata[e.target.name].status = e.target.getAttribute('data-status');\n\t\n\tthis.grapher.dispatch('input-change', data);\n   }\n   \n   render(){\n   \tconst mode = this.state.mode;\n\tconst parentMode = this.props.mode;\n\tconst handleSubmit = this.props.handleSubmit;\n\tconst children = this.state.children;\n\t\n\tlet addListeners = false;\n\tlet childInputProps = {\n\t\n\t};\n\t\n\tlet childButtonProps = {\n\t\n\t};\n\t\n\tif(parentMode == 'idle')\n\t\taddListeners = true;\n\t\t\n\tif(addListeners){\n\t\tchildInputProps = {\n\t\t\tonInputChange: this.onInputChange.bind(this),\n\t\t      \tonInputKeyDown: this.onInputKeys.bind(this)\n\t\t};\n\t\t\n\t\tchildButtonProps = {\n\t\t\tonButtonClick: (e) =\u003e {\n\t\t\t\tthis.grapher.dispatch('button-click')\n\t\t\t\tlet controls = this.state.controls;\n\t\t\t\t\n\t\t\t\tdelete controls[e.target.name];\n\t\t\t\t\n\t\t\t\thandleSubmit(controls);\n\t\t\t}\n\t\t}\n\t}\n\t\n\treturn (\n\t\u003cdiv className=\"formWrapper\"\u003e\n\t{/* https://mxstbr.blog/2017/02/react-children-deepdive/ */}\n\t{(parentMode === 'before-send') \u0026\u0026 \u003ch3\u003ePlease Wait....\u003c/h3\u003e}\n\t{(parentMode === 'sending') \u0026\u0026 \u003ch3\u003eSending To Server...\u003c/h3\u003e}\n\t{(mode === 'empty') \u0026\u0026 \u003cform name={name} method={this.props.method.toLowerCase()}\u003e\n\t\tChildren.map(children, (child, i) =\u003e {\n\t\t\tif(child.type === 'text' || child.type === 'checkbox') {\n\t\t\t\tchildInputProps.name = child.name;\n\t\t\t\tchildInputProps.type = child.type;\n\t\t\t\tchildInputProps.value = child.value;\n\t\t\t\treturn cloneElement(child, childInputProps);\n\t\t\t}else if(child.type === 'button' \u0026\u0026 (Children.count(children) === i + 1)){\n\t\t\t\tchildInputProps.text = child.text;\n\t\t\t\tchildInputProps.type = child.type;\n\t\t\t\treturn cloneElement(child, childButtonProps);\n\t\t\t}\n\t\t})\u003c/form\u003e}\n\t\n\t{(mode === 'filling' || mode === 'filled') \u0026\u0026 \u003cform name={name} method={this.props.method.toLowerCase()}\u003e\n\t\tChildren.map(children, (child, i) =\u003e {\n\t\t\tchildInputProps.value = this.formInputs[child.name].text;\n\t\t\tchildInputProps.status = this.formInputs[child.name].status;\n\t\t\tif(child.type === 'text' || child.type === 'checkbox') {\n\t\t\t\tchildInputProps.name = child.name;\n\t\t\t\tchildInputProps.type = child.type;\n\t\t\t\tchildInputProps.value = child.value;\n\t\t\t\treturn cloneElement(child, childInputProps);\n\t\t\t}else if(child.type === 'button' \u0026\u0026 (Children.count(children) === i + 1)){\n\t\t\t\tchildInputProps.text = child.text;\n\t\t\t\tchildInputProps.type = child.type;\n\t\t\t\treturn cloneElement(child, childButtonProps);\n\t\t\t}\n\t\t}) \u003c/form\u003e}\n\t\t\u003c/div\u003e );\n   }\n}\n\nexport default FormBox;\n\n```\n\n\u003e filename: TodoList.js\n```js\nimport React, { Component } from 'react';\nimport kahtox from 'kahtox';\n\nclass TodoList extends Component {\n\tconstructor(props){\n\t\tsuper(props);\n\t}\n\t\n\trender(){\n\t\tconst todos = this.props.todos;\n\t\tconst parentMode = this.props.mode;\n\t\t\n\t\treturn (\n\t\t\t\u003cdiv className=\"todoWrapper\"\u003e\n\t\t\t\t{(parentMode === 'before-fetch') \u0026\u0026 \u003ch3\u003ePlease Wait...\u003c/h3\u003e }\n\t\t\t\t{(parentMode === 'fetching') \u0026\u0026 \u003ch3\u003eFetching From Server...\u003c/h3\u003e }\n\t\t\t\t\u003cul\u003e\n\t\t\t\t{(parentMode === 'idle' \u0026\u0026 todos.length !== 0) \u0026\u0026 todos.map(function(todo){\n\t\t\t\t\treturn (\u003cli\u003e\u003ch3\u003e{todo.title}\u003c/h3\u003e\u003cp\u003e\u003cinput type=\"checkbox\" checked={todo.completed} /\u003e\u003cspan\u003e{todo.desc}\u003c/span\u003e\u003c/p\u003e\u003c/li\u003e);\n\t\t\t\t})}\n\t\t\t\t{(parentMode === 'idle' \u0026\u0026 todos.length === 0) \u0026\u0026 \u003cli\u003e\u003cspan\u003eYou have no Todos!\u003c/span\u003e\u003c/li\u003e}\n\t\t\t\t\u003c/ul\u003e\n\t\t\t\u003c/div\u003e\n\t\t);\n\t}\n}\n\nexport default TodoList\n\n````\n\n\u003e filename: TodoApp.js\n```js\nimport React, { Component } from 'react';\nimport ReactDOM from 'react-dom';\nimport kahtox from 'kahtox';\nimport thunk from 'redux-thunk';\nimport { createStore, applyMiddleware } from 'redux';\n\nimport Button from './Button.js';\nimport Input from './Input.js';\nimport FormBox from './Formbox.js';\nimport TodoList from './TodoList.js';\n\n\nfunction makeHttpRequestAndUpdate( payload, grapher, meta ) {\n  // Invert control!\n  // Return a function that accepts `dispatch` so we can dispatch later.\n  // Thunk middleware knows how to turn thunk async actions into actions.\n  return function() {\n    grapher.dispatch('http-req')\n    let url = payload.url;\n    delete payload.url;\n    return fetch(url, payload).then(\n      data =\u003e {\n      \tgrapher.dispatch('http-req-success', { todos: data });\n      },\n      error =\u003e {\n\tgrapher.dispatch('http-req-failure', error);\n      }\n    )\n  }\n}\n\nlet stateGraph = {\n   $initial: 'idle',\n   states:{\n\t   'idle': {\n\t\tsend:{ // HTTP POST, PUT, PATCH, DELETE\n\t\t\tnextState: 'before-send',\n\t\t\taction: '#thunk'\n\t\t},\n\t\tfetch:{ // HTTP GET, HEAD\n\t\t\tnextState: 'before-fetch',\n\t\t\taction: '#thunk'\n\t\t}\n\t   },\n\n\t   'before-fetch': {\n\t\t'http-req': { \n\t\t\tnextState:'fetching',\n\t\t\taction: null\n\t\t}\n\t   },\n\n\t   'before-send': {\n\t\t'http-req': {\n\t\t\tnextState:'sending',\n\t\t\taction: null\n\t\t}\n\t   },\n\n\t   'fetching':{\n\t\t'http-req-success':{\n\t\t\tnextState: 'idle',\n\t\t\taction: 'LOAD_TODOS'\n\t\t},\n\t\t'http-req-failure':{\n\t\t\tnextState: 'idle',\n\t\t\taction:null\n\t\t}\n\t   },\n\n\t   'sending': {\n\t       'http-req-success':{\n\t\t\tnextState: 'idle',\n\t\t\taction:'ADD_TODOS'\n\t\t},\n\t\t'http-req-failure':{\n\t\t\tnextState: 'idle',\n\t\t\taction:null,\n\t\t\tnotifyView: true\n\t\t}\n\t   }\n   }\n};\n\n\nlet store = createStore(function todos(state = [], action) {\n  switch (action.type) {\n    case 'LOAD_TODO':\n      return state.concat(action.todos);\n    case 'ADD_TODO':\n      return state.concat([action.todo])\n    default:\n      return state\n  }\n}, applyMiddleware(thunk));\n\nclass TodoApp extends Component {\n   constructor(prop){\n\tsuper(props);\n\tthis.grapher = kahtox.makeGrapher(stateGraph, (action, { payload, grapher, meta }) =\u003e { \n\t\tswitch(action){\n\t\t   case '#thunk':\n\t\t\treturn store.dispatch(makeHttpRequestAndUpdate(payload, grapher, meta))\n\t\t   case 'LOAD_TODOS':\n\t\t       return store.dispatch({ type:action, todos: payload }) \n\t\t   case 'ADD_TODO':\n\t\t       return store.dispatch({ type:action, todo: payload }) \n\t\t}\n\t});\n\tthis.state = {\n \t\tmode: this.grapher.initial,\n\t\tparentMode: null,\n\t\ttodos: store.getState()\n\t};\n\t\n\tlet select = (state) =\u003e state.todos;\n\t\n\tstore.subscribe(() =\u003e this.setState(prevState =\u003e Object.assign(prevState, { todos: select(store.getState()) })))\n\tthis.grapher.afterTransition((mode, data, isError) =\u003e {\n\t\t\n\t\tthis.setState(prevState =\u003e Object.assign(prevState, { mode }))\n\t})\n   }\n\n   componentDidMount(){\n\tthis.grapher.dispatch('fetch', { \n\t\turl: 'https://localhost:4005/todos',  \n\t\tcredentials: 'same-origin', \n\t\tmethod: 'GET', \n\t\tbody: { perPage: 5, page:0 } \n\t});\n   }\n   \n   submitDataToServer(formData){\n   \tthis.grapher.dispatch('send', { \n\t\turl: 'https://localhost:4005/todos', \n\t\tcredentials: 'same-origin', \n\t\tmethod: 'POST', \n\t\tbody:formData \n    \t});\n   }\n\n   render(){\n\tconst mode = this.state.mode;\n\tconst todos = this.state.todos;\n\n\treturn (\n\t\t{(mode === 'idle') \u0026\u0026 \u003cFormBox name=\"todos\" method=\"POST\" mode={mode} handleSubmit={this.submitDataToServer.bind(this)}\u003e\n\t\t   \u003cInput readonly=false name=\"todoTitle\" value=\"\" type=\"text\" pattern=\"[a-zA-Z0-9]+\" /\u003e\n\t\t   \u003cInput readonly=false name=\"todoDesc\" value=\"\" type=\"text\"  pattern=\"[a-zA-Z ]+\" /\u003e\n\t\t   \u003cInput readonly=false type=\"checkbox\" name=\"todoComplete\" value=\"\" /\u003e\n\t\t   \u003cButton type=\"button\" disabled={false} text=\"ADD\" /\u003e\n\t\t\u003c/FormBox\u003e \u003chr /\u003e \u003cTodoList todos={todos} mode={mode} /\u003e }\n\t\t{(mode === 'before-send' || mode === 'before-fetch') \u0026\u0026 \u003cFormBox name=\"todos\" method=\"POST\" mode={mode} handleSubmit={this.submitDataToServer.bind(this)}\u003e\n\t\t   \u003cInput readonly=false name=\"todoTitle\" type=\"text\" pattern=\"[a-zA-Z0-9]+\" /\u003e\n\t\t   \u003cInput readonly=false name=\"todoDesc\" type=\"text\" pattern=\"[a-zA-Z ]+\" /\u003e\n\t\t   \u003cInput readonly=false type=\"checkbox\" name=\"todoComplete\" /\u003e\n\t\t   \u003cButton type=\"button\" disabled={true} text=\"ADD\" /\u003e {/* Disable the button so submit event can't be triggere again */}\n\t\t\u003c/FormBox\u003e \u003chr /\u003e \u003cTodoList todos={todos} mode={mode} /\u003e }\n\t\t{(mode === 'sending' || mode === 'fetching') \u0026\u0026 \u003cFormBox name=\"todos\" method=\"POST\" mode={mode}\u003e\n\t\t   \u003cInput readonly=true name=\"todoTitle\" type=\"text\" pattern=\"[a-zA-Z0-9]+\" /\u003e \n\t\t   \u003cInput readonly=true name=\"todoDesc\" type=\"text\" pattern=\"[a-zA-Z ]+\" /\u003e\n\t\t   \u003cInput readonly=true type=\"checkbox\" name=\"todoComplete\" /\u003e \n\t\t   \u003cButton type=\"button\" disabled={true} text=\"ADD\" /\u003e {/* Disable the entire form */}\n\t\t\u003c/FormBox\u003e \u003chr /\u003e \u003cTodoList todos={todos} mode={mode} /\u003e }\n\t)\n   }\n}\n\nReactDOM.render(\u003cTodoApp /\u003e, document.body);\n```\n\n## License\n\nMIT\n\n## Contributing\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisocroft%2Fkahtox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisocroft%2Fkahtox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisocroft%2Fkahtox/lists"}