{"id":13806645,"url":"https://github.com/lifeart/hooked-components","last_synced_at":"2026-03-08T21:08:11.913Z","repository":{"id":48272145,"uuid":"156030792","full_name":"lifeart/hooked-components","owner":"lifeart","description":"Custom components for Ember, inspired by React Hooks approach","archived":false,"fork":false,"pushed_at":"2021-08-03T19:32:40.000Z","size":1664,"stargazers_count":31,"open_issues_count":19,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T18:17:34.619Z","etag":null,"topics":["ember","ember-addon","emberjs","functional-programming","functional-reactive-programming","react-hook","react-hooks"],"latest_commit_sha":null,"homepage":"","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/lifeart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-03T22:54:39.000Z","updated_at":"2024-05-30T02:40:33.000Z","dependencies_parsed_at":"2022-08-25T03:40:44.739Z","dependency_job_id":null,"html_url":"https://github.com/lifeart/hooked-components","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeart%2Fhooked-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeart%2Fhooked-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeart%2Fhooked-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeart%2Fhooked-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lifeart","download_url":"https://codeload.github.com/lifeart/hooked-components/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248546031,"owners_count":21122247,"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":["ember","ember-addon","emberjs","functional-programming","functional-reactive-programming","react-hook","react-hooks"],"created_at":"2024-08-04T01:01:14.236Z","updated_at":"2026-03-08T21:08:11.851Z","avatar_url":"https://github.com/lifeart.png","language":"TypeScript","readme":"hooks-component\n==============================================================================\n\nAddon used to experiment with `React Hooks` style APIs in Ember apps via\nexisting public APIs.\n\nInstallation\n------------------------------------------------------------------------------\n\n```\nember install hooks-component\n```\n\n\nThis addon provide 2 **DIFFERENT** - API's\n\n* React way hooks implementation (always call component function on rerender).\n* Ember way hooks implementation (call component function on first render only).\n\n\nUsage in React-Way\n------------------------------------------------------------------------------\nThe `hooks-component` API supports public React HooksAPI\n\n\n### Builtin hooks\n* `useEffect` -\u003e just like in React API\n* `useState` -\u003e just like in React API\n* `useLayoutEffect` -\u003e just like in React API\n--------\n* `getService` -\u003e `getService(serviceName)` -\u003e service lookup hook\n* `getController` -\u003e `getController(serviceName)` -\u003e controller lookup hook\n* `getRoute` -\u003e `getRoute(routeName)` -\u003e route lookup hook\n* `getStore` -\u003e store service lookup\n* `getOwner` -\u003e `getOwner()` -\u003e equals `getOwner(this)` in Ember.\n\n### Example\n\n```js\nimport { reactComponent, useEffect, useState } from \"hooks-component\";\n\nfunction ConferenceSpeakersReact() {\n\tconst [ speakers ] = useState(['Tom', 'Yehuda', 'Ed']);\n\tconst [ current, updateCurrent ] = useState(0);\n\n\tuseEffect(() =\u003e {\n\t\tconsole.log('dummy effect');\n\t});\n\n\tconst next = () =\u003e {\n\t\tlet nextSpeaker = current + 1;\n\t\tupdateCurrent(nextSpeaker);\n\t}\n\n\treturn {\n\t\tcurrentlySpeaking: speakers[current],\n\t\tmoreSpeakers: (speakers.length - 1) \u003e current,\n\t\tcurrent,\n\t\tnext, speakers\n\t}\n}\n\nexport default reactComponent(ConferenceSpeakersReact);\n\n```\n\n```hbs\n{{!-- app/templates/components/conference-speakers-react.hbs --}}\n\n\u003cdiv\u003e\n  \u003cp\u003eSpeaking: {{this.currentlySpeaking}}\u003c/p\u003e\n  \u003cul\u003e\n    {{#each speakers key=\"@index\" as |speaker|}}\n      \u003cli\u003e{{speaker}}\u003c/li\u003e\n    {{/each}}\n  \u003c/ul\u003e\n\n  {{#if this.moreSpeakers}}\n    \u003cbutton onclick={{action this.next this.current}}\u003eNext\u003c/button\u003e\n  {{else}}\n    \u003cp\u003eAll finished!\u003c/p\u003e\n  {{/if}}\n\u003c/div\u003e\n```\n\n### How to create custom hooks?\n\n* `getContextId` -\u003e `getContextId()` -\u003e get current instance context id (same between rerenders)\n* `getRerender` -\u003e return binded to current instance `update` function\n* `addBeforeCallTask` -\u003e execute some callback before component `update`\n* `addBeforeDestroyTask` -\u003e execute some callback before any component `destroy`\n\n```js\n\n// utils/custom-hook.js\n\nimport { getContextId, getRerender, addBeforeCallTask, addBeforeDestroyTask } from  \"hooks-component\";\n\nconst DUMMY_STORE = {};\nvar CALL_COUNTER = 0;\n\naddBeforeCallTask(()=\u003e{\n\tCALL_COUNTER = 0;\n});\n\naddBeforeDestroyTask(()=\u003e{\n\tconst uid = getContextId();\n\tif (uid in DUMMY_STORE) {\n\t\tdelete DUMMY_STORE[uid];\n\t}\n});\n\nexport function myCustomHook(componentStoreDefaultValue = {}) {\n\tconst uid = getContextId(); // current component instance ID\n\tconst hookCallId = CALL_COUNTER; // how many times hook called during rendering\n\tif (!(uid in DUMMY_STORE)) {\n\t\tDUMMY_STORE[uid] =  {}; // init store for component instance;\n\t}\n\tif (!(hookCallId in DUMMY_STORE[uid])) {\n\t\t// init store for exact call number inside component isntance;\n\t\tDUMMY_STORE[uid][hookCallId] = componentStoreDefaultValue;\n\t}\n\t// get current instance + callNumber state\n\tlet state = DUMMY_STORE[uid][hookCallId];\n\t// get rerender function (must be inside hook)\n\tlet rerender = getRerender();\n\t// increment hook call counter\n\tCALL_COUNTER++;\n\t// return current state for exact component and callNumber and update state function\n\treturn [ state, function(newState) {\n\t\tObject.assign(state, newState);\n\t\t// rerender will invoke component rerender\n\t\trerender();\n\t}\n}\n\n```\n\n```js\n\nimport { reactComponent } from \"hooks-component\";\nimport myCustomHook from \"utils/custom-hook\";\n\nfunction ConferenceSpeakersReact() {\n\tconst [ state , patchState ] = myCustomHook({ keys: 1 });\n\tconst [ fish, patchFish ] = myCustomHook({ salmon: 1 });\n\tconst { keys } = state;\n\tconst { salmon } = fish;\n\n\tconst next = () =\u003e {\n\t\tpatchState({\n\t\t\tkeys: keys + 1\n\t\t})\n\t}\n\n\tconst addSalmon = () =\u003e {\n\t\tpatchFish({\n\t\t\tsalmon: salmon + 1\n\t\t})\n\t}\n\n\treturn { keys, next, salmon }\n}\n\nexport default reactComponent(ConferenceSpeakersReact);\n\n```\n\n------------------------------------------------------------------------------\n\n\nUsage in Ember-Way\n------------------------------------------------------------------------------\n\nThe `hooks-component` API supports part of React hooks API, including:\n\n\tupdateContext - just like setProperties;\n\tuseEffect - do some calculation after dependent keys changed\n\textract - just like getWithDefault for component arguments\n\n`useEffect` - inside `component function` context support: function, tracked property paths in array-like style `['foo.length', 'foo', 'foo.firstObject']`;\n\nAll effects called during first render, on rerender effects called only if \"tracked\" property changed.\n\n### Example\n\n```js\n// app/components/conference-speakers.js (.ts would also work)\nimport hookedComponent from \"hooks-component\";\n\nfunction ConferenceSpeakers(attrs = {}) {\n\n\tconst { updateContext, useEffect, extract } = this;\n\n\tuseEffect(({current, speakers}) =\u003e {\n\t\tupdateContext({\n\t\t\tcurrentlySpeaking: speakers[current],\n\t\t\tmoreSpeakers: (speakers.length - 1) \u003e current\n\t\t})\n\t}, ['current'] );\n\n\tconst next = (current) =\u003e {\n\t\tcurrent++;\n\t\tupdateContext({\n\t\t\tcurrent \n\t\t});\n\t}\n\n\treturn extract(attrs, {\n\t\tnext,\n\t\tcurrent: 0,\n\t\tspeakers: ['Tom', 'Yehuda', 'Ed']\n\t});\n}\n\nexport default hookedComponent(ConferenceSpeakers);\n```\n\n```hbs\n{{!-- app/templates/components/conference-speakers.hbs --}}\n\n\u003cdiv\u003e\n  \u003cp\u003eSpeaking: {{currentlySpeaking}}\u003c/p\u003e\n  \u003cul\u003e\n    {{#each speakers key=\"@index\" as |speaker|}}\n      \u003cli\u003e{{speaker}}\u003c/li\u003e\n    {{/each}}\n  \u003c/ul\u003e\n\n  {{#if moreSpeakers}}\n    \u003cbutton onclick={{action next this.current}}\u003eNext\u003c/button\u003e\n  {{else}}\n    \u003cp\u003eAll finished!\u003c/p\u003e\n  {{/if}}\n\u003c/div\u003e\n```\n\n\n### useEffect API\n```ts\nfunction shouldRecomputeEffect(oldObject: object, newObject: object): boolean;\ntype Tracker = string | object | shouldRecomputeEffect;\ntype cleanupComputedEffect = undefined | Function;\nfunction computeEffect(newContext: any): cleanupComputedEffect;\n\nfunction useEffect(computeEffect, trakedItems?: Tracker | Tracker[] , useTrackersOnFirstRender?: boolean = false)\n```\n\n\n### How it's working?\nCurrent hookedComponents implementation logic:\n\n* We run `component function` only once, in component creation time.\n* `component function` accept named params (`args`) as first argument, and return `context object`.\n* `updateContext` method invoke existing effects and then, do `setProperties(currentContext, updatedProps)`.\n* if component `args` updated, it invokes `updateContext` method with updated `args`.\n* `useEffect` method adds \"after `updateContext` and before `setProperties` callbacks with `updatedProps` object as argument\"; \n* if `useEffect` call return function, it will be called before this effect call next time.\n* `updateContext` inside `useEffect` don't reinvoke effects, just patching `updatedProps` with new data.\n\nContributing\n------------------------------------------------------------------------------\n\n### Installation\n\n* `git clone \u003crepository-url\u003e`\n* `cd hooks-component`\n* `yarn install`\n\n### Linting\n\n* `yarn lint:js`\n* `yarn lint:js --fix`\n\n### Running tests\n\n* `ember test` – Runs the test suite on the current Ember version\n* `ember test --server` – Runs the test suite in \"watch mode\"\n* `ember try:each` – Runs the test suite against multiple Ember versions\n\n### Running the dummy application\n\n* `ember serve`\n* Visit the dummy application at [http://localhost:4200](http://localhost:4200).\n\nFor more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).\n\nLicense\n------------------------------------------------------------------------------\n\nThis project is licensed under the [MIT License](LICENSE.md).\n","funding_links":[],"categories":["Packages"],"sub_categories":["External Components Integration"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeart%2Fhooked-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flifeart%2Fhooked-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeart%2Fhooked-components/lists"}