{"id":21144330,"url":"https://github.com/gilbert/mithril-cc","last_synced_at":"2025-10-13T02:06:31.917Z","repository":{"id":57298408,"uuid":"351560942","full_name":"gilbert/mithril-cc","owner":"gilbert","description":"An opinionated library for writing Mithril components","archived":false,"fork":false,"pushed_at":"2023-03-12T19:49:49.000Z","size":32,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-11T02:42:27.962Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/gilbert.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-03-25T20:02:53.000Z","updated_at":"2024-08-03T10:18:57.000Z","dependencies_parsed_at":"2025-09-07T17:42:36.022Z","dependency_job_id":"7f10c3da-a387-412a-9f55-935a0b0a3335","html_url":"https://github.com/gilbert/mithril-cc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gilbert/mithril-cc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fmithril-cc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fmithril-cc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fmithril-cc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fmithril-cc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gilbert","download_url":"https://codeload.github.com/gilbert/mithril-cc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fmithril-cc/sbom","scorecard":{"id":426725,"data":{"date":"2025-08-11","repo":{"name":"github.com/gilbert/mithril-cc","commit":"814674ac22edb817bedf9cd359aceb166fa2d47f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/13 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-19T02:23:24.742Z","repository_id":57298408,"created_at":"2025-08-19T02:23:24.742Z","updated_at":"2025-08-19T02:23:24.742Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010112,"owners_count":26084692,"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-10-12T02:00:06.719Z","response_time":53,"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":[],"created_at":"2024-11-20T08:16:56.078Z","updated_at":"2025-10-13T02:06:31.900Z","avatar_url":"https://github.com/gilbert.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mithril-cc\n\nAn opinionated library for writing [Mithril.js](https://mithril.js.org) components.\n\n## Motivation\n\nMithril is the leader and forerunner of declarative HTML views **in plain old JavaScript**. However, its flourishing flexibility can leave one uneasy on the \"right\" way to do things. The wide array of available options all have different pros and cons that depend on the type of component you're writing.\n\nTo cure, CC compresses these options into a pleasant, one-size-fits-all approach, allowing you to trade discouraging decision fatigue for simple peace and tranquility.\n\nIn other words: Closure components are the epitome of userland Mithril, and CC brings out the best in them.\n\n## Getting Started\n\n```bash\nyarn add mithril-cc\n# or\nnpm install mithril-cc\n```\n\nIn your component files:\n\n```js\nimport {cc} from 'mithril-cc'\n```\n\n### Using a CDN\n\nIf you use a CDN, mithril-cc will be available via `m.cc`, `m.ccs`, etc.\n\n```html\n\u003cscript src=\"https://unpkg.com/mithril/mithril.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/mithril/stream/stream.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/mithril-cc\"\u003e\u003c/script\u003e\n```\n\n### TypeScript\n\nFor type inference, simply parameterize your `cc` calls:\n\n```ts\ntype Attrs = {\n  initialCount: number\n}\nconst Counter = cc\u003cAttrs\u003e(/* ... */)\n```\n\n## Learn by Example\n\n- [Simple counter](#simple-counter)\n- [View Attrs](#view-attrs)\n- [Component Setup](#component-setup)\n- [Reacting to Attrs Changes](#reacting-to-attrs-changes)\n- [Unsubscribe](#unsubscribe)\n- [Lifecycle Methods](#lifecycle-methods)\n- [`addEventListener`](#addeventlistener)\n- [`setTimeout` and `setInterval`](#settimeout-and-setinterval)\n- [React Hooks-like Abstractions](#react-hooks-like-abstractions)\n- [Shorthand Components](#shorthand-components)\n- [Island Components](#island-components)\n\n### Simple Counter\n\nState in a `cc` closure component is as simple as you could ever hope for. Set some variables, return a view thunk, and you're ready to go.\n\n```js\nimport m from 'mithril'\nimport {cc} from 'mithril-cc'\n\nconst Counter = cc(function(){\n  let n = 0\n  return () =\u003e [\n    m('p', 'Count: ', n),\n    m('button', { onclick: () =\u003e n++ }, 'Inc')\n  ]\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAwrQCuwgE6SAvJKz5q1ABRgF1YhHraAlMAA6aSZNhSragAyXrimMTmKrp1QD5JyZ2t1bQByAAcQikkAA1kFYkRJABJgNFZokwpA6yxQgCM5YmJ6SMlgSXpqKAhqAGtE7xU-NABqFsl2SRCASTRqEJNAgF1LVkG0Sw0seQZtABNaajkcBnw82jmATyi4pXHKGlosMOgYRTw8jDyYcio4G5gDIzQEHgBmRAAmADY2DhBMDg8Jo4AJDsJmDw2EMqNU0LVXqgAVw8FgIMRCIpoAcPOQeCRiGE4IgAPQkhRhWoAc00RxJaIxWKgAAFPvgHPgACz09GY6D4NFofD8A7ETZhbggODULFhUTsThAngSVzYHGKPEgAlE0nktCUml0LA8xnQVnsrkklUwbBW4iqjQiqhiiV4aWy+X-QGShl8qAAWi06s12uJZIp1Npxt9TMD1GZHIAnPhviS5hAJP7lnMSRA0HMYAAPYVgl2S90QOXQ1hAA)\n\n### View Attrs\n\nFor convenience, Mithril's `vnode.attrs` are made available directly as the first parameter to your view thunk.\n\n```js\nconst Greeter = cc(function(){\n  return (attrs) =\u003e (\n    m('p', `Hello, ${attrs.name}!`)\n  )\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIA4gCcYMRrMkBeSVnzVqACjABXNNWIR62gJTAAOmkmT5xPbJvaMxYrLhnVAPknbrtrZY2gDkAA4hFJIABgASMFBQtFEAJMCu7nD4mDisAITRZgGSRWispdYaWLQGxNoAJrTUejgM+ABGtPUAnlFWNpIAbhAwAO6Ifl4qvsFyCkp9kjkwEyEAglAQ1DAhkuXW+2iUNLRYYdAwsnjtGO0Jx3AJMEYmaAg8AJyIAAxsHCDLPCaOACE7CZg8NgAXSomzQAGt3qgAVw8FgIMRCLJoMdHOQeCRiGE4IgAPSkgxheEAc00p1J6Mx2KgAAEAEz4b74AAsDIxWOg+HRaHw-GOxG6YW4IDg1GxYVE7E4ODwEnk2FxsnxIEJxLJFLQVNpdCwfKZ0HZnJ5pLVMGwNvcdo0YqoEqlqrlEAVf2V0sZAqgAFotJrtbqSeTKTS6ab-czg9QWVyPvgAGyk+oQCSBlr1UkQND1GAAD1FoLd0tl8sVUNYQA)\n\nIn case you need it, vnode is provided as a second parameter.\n\n### Component Setup\n\nSometimes you need to set up state when your component gets initalized. CC provides your component with `attrs` **as a [stream](https://mithril.js.org/stream.html)**. Note how this is different from your view thunk, which receives `attrs` directly (as a non-stream).\n\nBecause it's a stream, your setup callbacks always have access to the latest value of you component's attrs.\n\n```js\nconst Counter = cc(function($attrs){\n  let n = $attrs().initialCount\n  return () =\u003e [\n    m('p', 'Count: ', n),\n    m('button', { onclick: () =\u003e n++ }, 'Inc')\n  ]\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAwrQCuwgE6SAvJKz5q1ABRgF1YhHraAJBmLFFcAJTAAOmkmTYUx2rMWr26-ghoIhhhQsgrEDk6KMMRyio7eqgB8ksjhTuraAOQADhkUkhkhDIj5eWjWFKlOWJkARnIW9LmSwJL01FAQ1ADWxfEqSWgA1IOS7PkAkmjUGdapALoOrLNoDhpY8gzaACa01HI4DPg1tFsAnnn2jpIAbhAwAO691onphYyKF5J+ARBBb8UARgADKNlksHJQaLQsFloDBFHgahgajByFQ4KiYAYjGgEDxgYgAExsDggTA4PCaOACKHCZg8NhzKgdNBdPGoMlcPBYAKERTQSExcg8EjELJwRAAeklCiyXQA5ppoZKecQ+dAAAKE-BA-AAFhVvP5UHwPLQ+H4kOIpyy3BAcGo-KyonYnApPAkkWwgsUwpAovFUplaDliroWENauNWp1+slnpg2HjlkTGktVGttrwDqdLtJ5LtqvVUAAtFofX6AxLpbKFUqI0XjWXqBrdQBOfAANklWwgEhL+y2kr8WxgAA8LTTM3acxBnYzWEA)\n\n### Reacting to Attrs Changes\n\nBecause top-level `attrs` is a [stream](https://mithril.js.org/stream.html), you can easily react to changes using `.map`.\n\nNote also the `this.unsub =` in this example. This will clean up your stream listener when the component unmounts. You can assign it as many times as you like; CC will remember everything.\n\n```js\nimport {cc, uniques} from 'mithril-cc'\n\nconst Greeter = cc(function($attrs){\n  let rank = 0\n  let renderCount = -1\n  this.unsub = $attrs.map(a =\u003e a.name).map(uniques()).map(n =\u003e rank++)\n  this.unsub = $attrs.map(() =\u003e renderCount++)\n\n  return (attrs) =\u003e (\n    m('p', `Hello, ${attrs.name}! You are person #${rank} (renderCount ${renderCount})`)\n  )\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIA4gCcYMRrMkBeSVnzVqACjABXNNWIR62gCQZixWXACUwADppJk2FNkY0Aa1WSADE4ubpLyaAAmMLIAwrQGUmoAtACMgZLEhBBw+AZwegBGvhZWNvhYGAAO2hiqAHySGPiYOLalFdoaBhAAjnrw2rYtZZXOKnUe3gDUE7ap6ZnZaLkFakXWWUPa-bUhTBHRsQxTM2ip8sR6ss5VxXbb2qkuWNoA5OXPFJIABgASMFBQtA+ZmAljWjS4rAAhJIAJqxerySTlSJweiSADEwPGXlYkm0oT2MTikixu0iRIYrFsn2OLmOVKcTmCyJsaLUzwAglAINQYM8nBosAdiNowrRqHocAx8HlaGEAJ4fRzOSQANwgMAA7og8bZtsgHuptHIFEolZImjAdSzUc4qRRDU9nnk9FZ6O9JMBJPRqNzqF4dVtRkiUWzJJy-XzJOxwybFJIuTy+bYHSrHi8XW60B6vT6-QHddsbWHngAhWh5Z7Rj7PONScuVlOpAC6TgZaEoNFoWHK0EieDyGDyf07cD+MCMJkWeAAnIgAEwJPyIPxsDggS14TRwARd4TMHhsZtUbneBAoTg4PBYCDpWTQTsXcg8EjEcpwRAAek-BnKXgA5po3afjed7QAAAvO+B+PgAAsIG3oQ95QKUEBoPg-CdsQ8rIngcDUPe5SiOwl7cCAEjyNgj6yM+ICvu+X4-mgf6AXQWAIWBUCQdBcGfhRMDYHx1gCRomFUNhuE8PhhHEeum48KBSHQAkWjUbR9Eft+v4AUB7GKchKnUOBMEzvgABsn5hJkxAJJKYSfmhEQAB4YbuElkdJEBEUerBAA)\n\n*Implementation detail: Because the `$attrs` stream gets updated before the view thunk, your view thunk will see the latest and correct version of your closure variables.*\n\n### Unsubscribe\n\nIf CC doesn't cover your cleanup use case, you can assign `this.unsub =` to any function. CC will run the function when the component unmounts.\n\n```js\nconst UnmountExample = cc(function(){\n  this.unsub = () =\u003e console.log(\"unmount\")\n  return () =\u003e m('div', 'UnmountExample')\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAqmiy0ArgwCiAD2wAHWJIC8krPmrUAFGCXViEesYCUwADppJk4oQhx8SuAoBGuyba6AHyS4rSw+FC0AObG9iBK8krE8TaOzgBOMMQKGU6BOiFYxgDkACYQAG4lFJIlskkq6lhaMCVpaKwdjrBScIS0AO7+xBkKMI4GjcTGZbTUCjgM+D60ZQCetQ5OkpUQMIOIATbBksjpzvqlPgrExPQ1ksCS9NRQENQA1kcFIf1D-gAhP9huxJCDJAB+OoNRQMEqSI4lACycOI7QoF2cEIAZDirrDkmpNLAOs4ALqOLqOSg0WgtaAwDJ4HwYHwwchUOAcmAWKxoBA8ACMQsQACY2BwQJgcHhDHABHThMweGxyVR3mhPoLUNKuHgsBBXBloLTcuQeCRiBo4IgAPR2pQaT7RQz0u2G43QAACYvwAAZ8AAWD1GwgmqD4Q1ofD8WnEdYabggODUE0aUTsTiyngSLLYM0ZC0gK02+2OtDO110LChr1QX0B4N2vMwbAt0ZtgxxqgJpN4VPpzNSmXJz3h6AAWiMheLpdtDqdLrdtfHEen1G9gYAnPgAOx2ioSSeLMp2iBoMowVSxxV95ODiAZtWsIA)\n\n### Lifecycle Methods\n\nEven though you're using view thunks, you still have access to all of Mithril's lifecycles via `this`. You can even call `oncreate` and `onupdate` multiple times, which can be useful for creating [React Hooks-like abstractions](#user-content-react-hooks-like-abstractions).\n\n```js\nconst HeightExample = cc(function(){\n  let height = 0\n  this.oncreate(vnode =\u003e {\n    height = vnode.dom.offsetHeight\n    m.redraw()\n  })\n  return () =\u003e m('p', `The height of this tag is ${ height || '...' }px`)\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAEjAgBzEgFEAHtgAOsSQF5JWfNWoAKMAFc01YhHpGAlMAA6aSZNhTCcxVN0AGJy+JCCDh8emoAJxgMRiMANzRaABMYHQA+SUdnF0kPBRIdSXikmHxE2n1aMDA4GGJZPOJ-bP1IxPCMAHc7JtZbJsjiU3DnOzS9IwBydQmKSQADABUPHM98yslA4I2MeUktgBJgFYbJAB9TyQn8a4nJVnUVOb60XqcnfSxac2IjMupTHAMfAAIySAE9ZvUvKoNLBnpQaOV1NAYOE8MCMMCYOQqDVYJZrGgEDwAIw+RA+NgcECYHB4AxwASI4TMHhsAC6VCgEDQAGtiagaVw8FgIIFwtAEUNyDwSMR1HBEAB6JXmdS8+QGcpK0Xi6AAAQATPgfPgACw6sWECVQfCitD4fgI4hg9TcEBwCIQdSidicOk8CSRbBS8IykByhXK1VodWauhYS16qBGk3mpVBqKJzPYR1Ml1uvCeiU+qn+92663QAC0hlD4cjipVao1WsTlZtteo+tNAE58AB2JWJYLEasAxJKnnJFR552u93F72+9msIA)\n\n### addEventListener\n\nOften times you need to listen for DOM events. With `this.addEventListener`, CC will automatically clean up your listener when the component unmounts. It will also call `m.redraw()` for you.\n\n```js\nconst MouseCoords = cc(function(){\n  let x = 0, y = 0\n\n  this.addEventListener(window, 'mousemove', event =\u003e {\n    x = event.offsetX, y = event.offsetY\n  })\n  return () =\u003e m('p', `Mouse is at ${x}, ${y}`)\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAsrQCucGAGFatAE4ATOJIC8krPmrUAFGDlpqxCPWMBKYAB00kybCkAPXZIAMFSQE8vbycnF2JCCDh8DA0NAFEANxEAGUjGNBg1YwB3CDQNWmy-AHIseUUypOK-GCSGXQA+SUdnF0lPPVqRfFowMEViAA0-QM664h6+gYBNUMlWWzm1GGI5NWc7Rv1jYoAHaskAA1kFGElIyQwpABJgd3ZJW-9WQ8W0BZC0AzLzYmMC6hyHAMfAAI1oGn8fhOihU6i0b0oNFoWF20EyeFBGFBMHIVEUsEs1jQCB4AEZvIhvGwOCBMDg8IY4AJkcJmDw2ABdKhQPIAa1JqDpXDwWAg4TU0CRa3IPBIxF2cEQAHpleZdnyAOaGFHKsUS6AAAQATPhvPgACx68WESVQfBitD4fhI4j+XbcEBwaiS3aidicBk8CTLbDStSykDyxUqtVoDXauhYa0GqAms2W5UhmDYLPEUMGF1UN0evDe33+2n0z3623QAC0RnDkejStV6q1OuTtbtjeohvNAE58AB2ZUaNL1oEaZV5DQwdzOlklz3liB+rmsIA)\n\n### setTimeout and setInterval\n\nJust like [this.addEventListener](#user-content-addeventlistener), you can use `this.setTimeout` and `this.setInterval` to get auto cleanup and redraw for free.\n\n```js\nconst Delayed = cc(function(){\n  let show = false\n  this.setTimeout(() =\u003e {\n    show = true\n  }, 1000)\n  return () =\u003e m('p', `Show? ${show}`)\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAIjCgYAnjAAmkgLySs+atQAUYAK5pqxCPV0BKYAB00kybClxCtAO7rJYDFDgxb94kIIOHxfYgAVCBxaA2JdS3UAPkkbO3tJZzcPYgAnAz809kkARgAGcot-SRyYYgMcuwS1ZKxdAHIABzaKSQADAGUXVwB+SQASYEzXVl7KtFY52y0sGIZdZVpqAxwGfAAjWmUFHtl5JWU5yhpaLA7oGBy8PYw9uSvfWBMzNAQeAE5EKU2BwQJgcHhtHABNdhMweGwALpUKAQNAAa1+qFBXDwWAggRy0Cu9XIPBIxA6cEQAHpqUYOmiAObaG7UvEE6AAAQATPhSvgACxs-GEQlQfB4tD4fhXYgKDrcEBwaiEjqidiccE8CQ1bDEnKkkDkyk0uloBnMuhYYUcqA8vmC6k6mDYJ25F1aGVUOUKvDK1XqkFgxXs0XQAC0On1huNVNp9KZLOtobFkeonP5f3wAHZqcpgsRw9tlNTUcoYAAPaXQn2K-0QNWI1hAA)\n\n```js\nconst Ticker = cc(function(){\n  let tick = 0\n  this.setInterval(() =\u003e tick++, 1000)\n  return () =\u003e m('p', `Tick: ${tick}`)\n})\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAKhGoBrGACdJAXklZ81agAowAVzTViEejoCUwADppJk2FJMK1kgAw27xQhDj44MYgBJYSUANwwoHQs1AD5JJ3kAakSKSQBGV0zzD0klAP0lW2jVOKwdAHIAB3LUgANZBURJABJgBNZa7LRWLptNLFpDYh0AE1pqfRwGfAAjWhGAT1SGxSUuyhpaLEroZTwZjBmYcip-WGNTNAQeAHZEVzYOEEwcPC04AU3hZh42AF0qFAIGh5NdUM8uHgsBAvEpoBsCuQeCRiJU4IgAPQYwyVeQAcy0Wwx0Nh0AAAgAmfCufAAFmJMMIcKg+GhaHw-A2xAWlW4IDg1DhlVE7E4rx4Ejy2ARSiRIBRaMx2LQuIJdCwDNJUEp1LpGMlMGw+uIUs0nKo3N5eAFQpFTxefJJTOgAFptDK5Qr0VicfjCRqncy3dQyTSAJz4G4YkY+YguyYjDHAkYwAAeHM+lr5Nogwv+rCAA)\n\n### React Hooks-like Abstractions\n\nBecause CC's `this` has everything you need to manage a component, you can abstract setup and teardown behavior like you would using React hooks.\n\nFor example, we can refactor the [MouseEvents example](#user-content-addeventlistener) into its own function:\n\n```js\nimport {cc} from 'mithril-cc'\nimport Stream from 'mithril/stream'\n\nconst MouseCoords = cc(function(){\n  let [$x, $y] = useMouseCoords(this)\n  return () =\u003e m('p', `Mouse is at ${$x()}, ${$y()}`)\n})\n\nfunction useMouseCoords(ccx) {\n  const $x = Stream(0), $y = Stream(0)\n\n  ccx.addEventListener(window, 'mousemove', event =\u003e {\n    $x(event.offsetX); $y(event.offsetY)\n  })\n\n  return [$x, $y]\n}\n```\n\n[Live demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4hcYgAIAsrQCucGAGFatAE4ATOJIC8krPmrUAFGDlpqxCPWMBKYAB00kybCnIAJAA8KkjwE8AXV1JBRhZMJV1LWNiQgg4WycXNRhiOTVnO10APn1jAHIABwLfAAMIxUkEyQwpD2BvO3Y-Rv9msqS0Vi6nMwsrelDFSuVVTThjIy9bSUdnSXF6rxCDCVTsYwAGW18A1fx1mE2dp2TF6i98DA0NAFEANxEAGQTGNBg1YwB3CDQNWjfXwFLDyRSgp6lSQwJ4MXJzc4uJowkT4WhgMCKYgADVsAG4-O0UQw0RisQBNLouHpnBapdKZSSeHyEwJOVi0gyg8zEYwA6hyHAkgBGtA0-l8oyiEy6lBotCwRWgnzwwowwpg5CoilglmsaAQPAAnIgACxbNgcECYHB4QxwATy4TMHhsQJUKB-ADWhtQ1q4eCwEDiamgcoy5B4JGIRTgiAA9PHzEUvQBzQwK+NBkPQAACACZ8Ft8Kas8HCKGoPgg2h8Pw5cR-EVuCA4NRQ0VROxOLaeEdsOG1JGQNHYwmk2gU+m6FgyzmoAWiyX4-3Z6u647G828G2O12rTaW9mK9AALRGQfD0dxxPJtMZ2fHyvn6i54tG-AAdnjGjep8FGjxn8GgwFc9ZUFuLa7hAnZuqwQA)\n\n### Shorthand Components\n\nIf you only need attrs and nothing else, you can use `ccs`.\n\n```js\nimport {ccs} from 'mithril-cc'\n\nconst Greeter = ccs(attrs =\u003e (\n  m('p', `Hello, ${attrs.name}!`)\n)\n```\n\n### Island Components\n\nMithril's best feature is how it recalculates the entire app tree when it redraws. This makes your dev life easy by reducing a significant amonut of boilerplate code, and leaving less room for out-of-sync state-to-view bugs, all while having great performance 98% of the time.\n\nHowever, [in rare cases](https://mithril.js.org/lifecycle-methods.html#avoid-premature-optimizations) you may need to optimize for fewer redraws to fix a poor performance behavior. Islands are components that only redraw themselves instead of the whole app tree.\n\nIslands are not necessary unless your app is rendering with a high redraw rate, such as (maybe) render-on-keystroke.\n\nLogistics-wise, YOUR COMPONENT MUST ONLY HAVE A SINGLE, STABLE ROOT ELEMENT. You cannot, for example, return an array of elements from your component, or return an element sometimes and null other times.\n\nThis should be used as sparingly as possible. When used, your component should ONLY modify its own state, and not any state read by other components outside your component's descendants.\n\nWith that said, here is how to use it:\n\n```js\nimport m from 'mithril'\nimport {ccIsland} from 'mithril-cc'\n\nconst Counter = ccIsland(function(){\n  let n = 0\n  return () =\u003e [\n    m('p', 'Count: ', n),\n    m('button', { onclick: () =\u003e n++ }, 'Inc')\n  ]\n})\n```\n\nAs you can see, your component behaves like any other cc, given the caveats described above.\n\n## Developing\n\n```bash\nnpm run build\ncd pkg\nnpm publish\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbert%2Fmithril-cc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgilbert%2Fmithril-cc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbert%2Fmithril-cc/lists"}