{"id":18447077,"url":"https://github.com/zot/react-mvc-lite","last_synced_at":"2025-08-12T12:34:46.616Z","repository":{"id":66781226,"uuid":"412555716","full_name":"zot/react-mvc-lite","owner":"zot","description":"Lightweight, low-boilerplate, fine-grained MVC for React","archived":false,"fork":false,"pushed_at":"2021-10-04T19:08:11.000Z","size":829,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-23T03:48:45.025Z","etag":null,"topics":["mv","react"],"latest_commit_sha":null,"homepage":"https://zot.github.io/react-mvc-lite/","language":"TypeScript","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/zot.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":"2021-10-01T17:15:19.000Z","updated_at":"2021-10-04T01:08:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"11afad56-6dab-451a-8cd0-96e62d314b80","html_url":"https://github.com/zot/react-mvc-lite","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zot/react-mvc-lite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zot%2Freact-mvc-lite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zot%2Freact-mvc-lite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zot%2Freact-mvc-lite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zot%2Freact-mvc-lite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zot","download_url":"https://codeload.github.com/zot/react-mvc-lite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zot%2Freact-mvc-lite/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270061683,"owners_count":24520346,"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","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["mv","react"],"created_at":"2024-11-06T07:11:53.819Z","updated_at":"2025-08-12T12:34:46.574Z","avatar_url":"https://github.com/zot.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [React-mvc-lite](https://github.com/zot/react-mvc-lite): A simple, fine-grained MVC approach for React\n\n## Intro\n\nFine-grained MVC can provide unparalleled flexibility and dynamism\nsince it relies on object oriented message dispatching to render\ncomponents and it can also help with robustness (more modular code,\nless case logic). This tiny project implements MVC in React in an\nextremely simple way with ***very low overhead*** and ***very low\nboilerplate***.\n\nTo use this package, you just define model classes with rendering\nmethods. If you want or need a strict separation of presentation and\ndomain, you can make separate view classes but you'll have provide a\nway to bridge from the models to the views. When it's practical, I\nadvocate just putting the rendering methods in the models.\n\nThe MVC pattern was originally created in the 70s, for Smalltalk. The\n[paper](https://www.ics.uci.edu/~redmiles/ics227-SQ04/papers/KrasnerPope88.pdf)\nwas published in 1988. There are several MVC approaches out there for\ndoing MVC in React but my goal here is to provide ***lightweight***,\n***low boilerplate***, ***fine-grained*** MVC for React.\n\nHere's the general idea of MVC (courtesy of Wikimedia):\n\n\u003ca href=\"https://en.wikipedia.org/wiki/File:MVC-Process.svg\"\u003e![MVC](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/MVC-Process.svg/400px-MVC-Process.svg.png)\u003c/a\u003e\n\nWeb apps use this approach today but to many developers, the \"view\" is\nsimply the *entire web page*. The controller processes the web request\nand consults models in the back-end to build the \"view\". This pattern\nworks well but many apps could benefit from a finer-grained approach.\n\nIn Smalltalk, each widget has a model, view, and controller. This\nincludes text fields, lists, buttons, menus, items in menus, and so\non. The model for a text field holds a string and when the model\nchanges, it automatically updates its view (the text field). Also, in\nSmalltalk, views contain views. What we think of today as \"widgets\",\nare views in Smalltalk, in the MVC sense.\n\nSo, instead of leaving web apps at a bulk approach, with only one\nview, the frontend can be comprised of many models, views, and\ncontrollers.  This captures more of the original intent, flexibility,\nand power of the MVC paradigm (see the paper for details).\n\nNormally, in a React app, you make components for data you want to\nrender and each component references components for the data it uses\ndirectly, \"inlining\" them, so to speak.\n\n## Example\n\nYou can see the example in action [here](https://zot.github.io/react-mvc-lite), by the way.\n\nFor this example, we'll use 3 types of counters: IncrementingCounter,\nDecrementingCounter, and IncDecCounter. The three counters really only\ndiffer in how they render (what buttons and how many of them) but in real\nlife cases, the different objects would also vary in properties and behavior.\n\n![Three Counters](images/basic-page.png)\n\n*By the way, I do realize that my example is also a counter example.*\n\nFor the inlining example, we end up with something like this:\n\n```typescript\nfunction InlineCounters() {\n    return (\n        \u003c\u003e\n            {counterList.map((c, i)=\u003e \u003cInlineCounter counter={c} i={i}/\u003e)}\n        \u003c/\u003e\n    )\n}\n\nfunction InlineCounter({i, counter: c}: {i: number, counter: Counter}) {\n    const [value, setValue] = useState(c.value)\n\n    return (\n        \u003cdiv className='mb-4' key={`inline-${i}`}\u003e\n            {c.hasInc ?\n                \u003cButton\n                    variant='contained'\n                    onClick={()=\u003e{c.value++; setValue(c.value)}}\u003e\n                    Inc\n                \u003c/Button\u003e\n                : \u003c\u003e\u003c/\u003e}\n            {c.hasInc \u0026\u0026 c.hasDec ? \u003c\u003e\u0026nbsp;\u003c/\u003e : \u003c\u003e\u003c/\u003e}\n            {c.hasDec ?\n                \u003cButton\n                    variant='contained'\n                    onClick={()=\u003e{c.value--; setValue(c.value)}}\u003e\n                    Dec\n                \u003c/Button\u003e\n                : \u003c\u003e\u003c/\u003e}\n            \u0026nbsp;\n            {c.name}: {c.value}\n        \u003c/div\u003e)\n\n}\n```\n\nThere's nothing wrong with this approach and in many cases, this is\nmore practical and simpler than defining classes to represent\ndifferent configurations. The approach, however, is not very \"OO\"; it\nuses case logic that you could avoid by using methods, maybe by\ndefining `renderInc()` and `renderDec()`. The fundamental structure\nalong with the state mangament, however, is in the InlineCounter\nfunction. This means the counters aren't in charge of their own state\n-- even if you provided `renderInc()` and `renderDec()`, they couldn't\ncall `useState()` except in very limited ways. If some counters, for\nexample, allow editing their name, they would need state for that but\nothers would not need it.\n\nIt takes a little discernment to decide whether it's fitting to make\ndifferent classes for your model. When you need to, you can step out\nof \"inline\" mode and go into \"MVC\" mode. In MVC, different kinds of\nmodels have different kinds of views in ***different situations*** and\nthere should be a straightforward way to choose the view for your\nmodel, like by just asking the model to render itself. A model could\nrender itself differently depending on whether you're presenting an\neditor, a list element, or a select item for it.\n\nHere's what `Counters()` looks like, an MVC version of\n`InlineCounters()`. By calling `c.renderEditor()`, we let the model\ndecide how to render itself.\n\n```typescript\nfunction Counters() {\n    return (\n        \u003c\u003e\n            {counterList.map((c, i)=\u003e c.renderEditor(i))}\n        \u003c/\u003e\n    )\n}\n```\n\nThe `renderEditor()` method does have to use a *tiny bit* of ceremony\nin order to interact properly with React because when you need to mange state, you have to \nuse a component; methods aren't React components.\n\n```typescript\nrenderEditor(index: number) {\n    return (\u003cRender key={`inc-${index}`} render={()=\u003e {\n        const controller = control(this, 'name', 'value')\n\n        console.log('render', this)\n        return (\n            \u003cdiv className='mb-4'\u003e\n                \u003cButton\n                    variant='contained'\n                    onClick={controller(()=\u003e this.value++)}\u003e\n                    Inc\n                \u003c/Button\u003e\n                \u0026nbsp;\n                {this.name}: {this.value}\n            \u003c/div\u003e\n        )\n    }}/\u003e)\n}\n```\n\nThe `Render` component provides a way to manage state from a method,\nit simply delegates rendering to the function you give it. The\n`checkState` function captures the state of the model -- it uses\n`deepEqual` to detect state changes so you can give it properties that\ncontain structured data.\n\nThe controller is a function returned from the `control()` function,\nwhich also captures the state of the model. In Smalltalk's MVC,\ncontrollers process input events, so this is an analgous technique --\nif your event handling is complex, you can gather it all into one\nplace instead of just using expressions like I do in the example.\n\n## Using this in your code\n\nSo, that's it. Just use define your model and add rendering methods\nthat use `\u003cRender\u003e` and `controller()`.\n\n# Building the example\n\n- build the dist directory with `npm run dist`\n- run a webpack server with `npm run http`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzot%2Freact-mvc-lite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzot%2Freact-mvc-lite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzot%2Freact-mvc-lite/lists"}