{"id":19465407,"url":"https://github.com/michael-klein/hookuspocus","last_synced_at":"2025-04-25T09:31:56.551Z","repository":{"id":34069726,"uuid":"168335550","full_name":"michael-klein/hookuspocus","owner":"michael-klein","description":"hooks for all the functions!","archived":false,"fork":false,"pushed_at":"2023-01-03T16:24:55.000Z","size":1185,"stargazers_count":62,"open_issues_count":18,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-29T02:05:04.713Z","etag":null,"topics":["functional","functional-js","functional-programming","hooks","javascript","react"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/michael-klein.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}},"created_at":"2019-01-30T11:57:42.000Z","updated_at":"2023-03-10T17:05:02.000Z","dependencies_parsed_at":"2023-01-15T04:23:48.204Z","dependency_job_id":null,"html_url":"https://github.com/michael-klein/hookuspocus","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/michael-klein%2Fhookuspocus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-klein%2Fhookuspocus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-klein%2Fhookuspocus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-klein%2Fhookuspocus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michael-klein","download_url":"https://codeload.github.com/michael-klein/hookuspocus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250790158,"owners_count":21487759,"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":["functional","functional-js","functional-programming","hooks","javascript","react"],"created_at":"2024-11-10T18:19:58.824Z","updated_at":"2025-04-25T09:31:56.306Z","avatar_url":"https://github.com/michael-klein.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://i.imgur.com/G3AHpOs.png\" width=\"250px\" alt=\"hookus pokus logo\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eAdd hooks to all the functions!\u003c/b\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/hookuspocus\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/hookuspocus.svg\"\u003e\u003c/a\u003e \u003ca href=\"https://travis-ci.org/michael-klein/hookuspocus\" target=\"_blank\"\u003e\u003cimg src=\"https://travis-ci.org/michael-klein/hookuspocus.svg?branch=master\"\u003e\u003c/a\u003e \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/hookuspocus.svg\"\u003e \u003cimg src=\"https://img.shields.io/npm/dt/hookuspocus.svg\"\u003e \u003cimg src=\"https://img.shields.io/npm/l/hookuspocus.svg\"\u003e\n\u003c/p\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nThis is a small JavaScript library that will allow you to add [hooks](https://reactjs.org/docs/hooks-intro.html) to any function. It clocks in at less than `1kb` minified and gzipped.\n\nIt provides the basic hooks from which you can build more complex hooks:\n\n- useReducer\n- useState\n- useLayoutEffect\n- useEffect\n\n# Basic usage\n\nIf you don't know what they are yet, please learn about hooks from the [react docs](https://reactjs.org/docs/hooks-intro.html) before you continue.\n\nIn order to enhance any function with hooks, you need to call it through `pocus`:\n\n```javascript\nimport { pocus, useState } from \"hookuspocus\";\nfunction withHooks() {\n  const [count, setCount] = useState(0);\n  console.log(`function called ${count} times`);\n  setCount(count + 1);\n}\npocus(withHooks); // function called 0 times\npocus(withHooks); // function called 1 times\npocus(withHooks); // function called 2 times\n```\n\nIt's important that the function you pass each pocus run is the same object every time (because we use it as key to store hook state across runs). If you can't guarantee this, you may pass a `context object` as second argument to connect subsequent runs:\n\n```javascript\nimport { pocus, useState } from \"hookuspocus\";\nconst context = { foo: \"bar\" };\nfunction withHooks() {\n  const [count, setCount] = useState(0);\n  console.log(`function called ${count} times`);\n  setCount(count + 1);\n}\npocus(withHooks, context); // function called 0 times\npocus(withHooks, context); // function called 1 times\npocus(withHooks, context); // function called 2 times\n```\n\nYou can also pass `arguments` to the wrapped function call by supplying them in an array as the first argument:\n\n```javascript\nimport { pocus } from \"hookuspocus\";\nfunction withHooks(arg1, arg2) {\n  console.log(`${arg1} ${arg2}!`); // Hello world!\n}\npocus([\"Hello\", \"world\"], withHooks);\n```\n\nInternally, hookuspocus uses WeakMaps if possible to keep states between runs (and falls back to simple Maps). If your target browsers don't all have support for WeakMaps or you need to trigger `cleanUp` logic (e.g.: from useEffect) after a final run, you can call pocus with `true` as the last argument. This will call all remaining `cleanUp` functions and remove the function/context from the map:\n\n```javascript\nimport { pocus, useEffect } from \"hookuspocus\";\nfunction withHooks() {\n  useEffect(() =\u003e {\n    //some effect here\n    return () =\u003e {\n      //something that needs cleaning up here\n    };\n  });\n}\npocus(withHooks); //run it\npocus(withHooks, true); //clean up\n```\n\nhookuspocus also exports a helper function called `fidibus` that allows you to wrap a function (and context) and returns a new function you can just call repeatedly, for ease of use:\n\n```javascript\nimport { fidibus } from \"hookuspocus\";\nconst wrapped = fidibus((arg1, arg2) =\u003e {\n  const [count, setCount] = useState(1);\n  console.log(`${arg1} ${arg2} #${count}!`);\n  setCount(count + 1);\n});\nwrapped(\"hello\", \"world\"); // Hello world #1!\nwrapped(\"hello\", \"world\"); // Hello world #2!\nwrapped(\"hello\", \"world\"); // Hello world #3!\n```\n\nThe wrapped function also has a `cleanUp` method attached to it:\n\n```javascript\nwrapped.cleanUp(); // runs cleanUp logic\n```\n\n# Creating hooks\n\nThere are two way to create hooks for use with hookuspocus:\n\n1. By composing new hooks from existing hooks. Hooks created in this manner from other frameworks like React should just work with hookuspocus (as long as they don't rely on built in hooks that are not provided by hookuspocus).\n\nThe useState hook is actually example of a hook created this way:\n\n```javascript\nimport { useReducer } from \"./use_reducer\";\n\nexport const useState = initialState =\u003e {\n  const [state, dispatch] = useReducer(\n    (_, action) =\u003e action.value,\n    initialState\n  );\n  return [\n    state,\n    newState =\u003e\n      dispatch({\n        value: newState\n      })\n  ];\n};\n```\n\n2. By creating new base hooks that use features provided by hookuspocus. This is accomplished with the `hookus` method. Let's look at the useEffect hook as an example:\n\n```javascript\nimport { hookus } from \"./core\";\nexport const useEffect = hookus((data, effect, values) =\u003e {\n  if (\n    !data.v ||\n    (values \u0026\u0026\n      !(\n        values.length === data.v.length \u0026\u0026\n        values.every(value =\u003e ~data.v.indexOf(value))\n      ))\n  ) {\n    data.v = values;\n    if (data.cleanUp) {\n      data.cleanUp();\n    }\n    data.after = () =\u003e {\n      data.cleanUp = effect();\n    };\n  }\n});\n```\n\n`hookus` takes a function which represents the implementation of the hook and returns a hook function. Whenever this hook is called, the wrapped function will be called with a data object followed by whatever arguments where passed to the wrapper.\n\nThe data object is the means by which hooks can interact with the hookuspocus api and persist data between pocus calls. You can add any property to the data object for the latter purpose (in the above example `data.v` is used to store the values array passed as second argument to `useEffect`).\n\nThe data object also accepts 3 function properties: `before`, `after` and `cleanup`. Methods that are passed to these will be called before the next hook call, after the current pocus run or respectively when cleanUp is initiated.\n\n`useEffect` uses `data.after` to execute effects after pocus runs and will manually call cleanUp before applying new effects if neccessary.\n\n## Intercepting hook calls\n\nYou might need to run some custom code when certain existing hooks are called. For instance, a view library might want to queue a re-render when setState from useState was called. For this purpose, hookuspocus provides the `on` method:\n\n```javascript\nimport { on, useReducer } from \"./core\";\n\non(useReducer, (data, reducer, initialArg, init) =\u003e {\n  const [state, dispatch] = data.hook(data, reducer, initialArg, init);\n  return [\n    state,\n    action =\u003e {\n      const result = dispatch(action);\n      if (state !== result) {\n        //do something when the state has changed\n      }\n      return result;\n    }\n  ];\n});\n```\n\n`on` basically allows you to wrap the existing hook with your own logic. You pass it the hook you want to intercept and a callback that will receive whatever arguments are passed to the hook at runtime.\n\n**Important**: `data.hook` is the original hook function and you should always call this in case you need to perform the normal hook functionality in `on`. If you call `useReducer`, for example, in the above code, you will cause an infinite loop.\n\nIncidentally, the above code is now by default provided by hookuspocus in the `onStateChanged` method:\n\n```javascript\nimport { onStateChanged } from \"./core\";\n\nonStateChanged(() =\u003e {\n  // do something\n});\n```\n\n### License\n\nMIT License\n\nCopyright (c) 2019 Michael Klein\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichael-klein%2Fhookuspocus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichael-klein%2Fhookuspocus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichael-klein%2Fhookuspocus/lists"}