{"id":21574048,"url":"https://github.com/ln613/hookompose","last_synced_at":"2025-08-20T12:40:39.032Z","repository":{"id":57266568,"uuid":"198128005","full_name":"ln613/hookompose","owner":"ln613","description":"Hookompose allows you to call hook functions in a recompose style, giving you the best of both worlds.","archived":false,"fork":false,"pushed_at":"2020-08-14T03:38:21.000Z","size":118,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-25T03:16:48.900Z","etag":null,"topics":[],"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/ln613.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-07-22T01:57:22.000Z","updated_at":"2020-08-14T03:38:02.000Z","dependencies_parsed_at":"2022-08-25T03:41:09.610Z","dependency_job_id":null,"html_url":"https://github.com/ln613/hookompose","commit_stats":null,"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ln613%2Fhookompose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ln613%2Fhookompose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ln613%2Fhookompose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ln613%2Fhookompose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ln613","download_url":"https://codeload.github.com/ln613/hookompose/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244173498,"owners_count":20410295,"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":[],"created_at":"2024-11-24T12:08:32.628Z","updated_at":"2025-03-18T06:42:49.132Z","avatar_url":"https://github.com/ln613.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hookompose\n\nReact _hooks_ allow you to use local states and effects in a functional component, but your functional component is no longer pure.\n\n_Recompose_ serves the same purpose as hooks by providing enhancer functions (which are higher order components) to enhance your pure functional components with local states and effects. Your functional component is kept pure, but recompose suffers the HOC wrapper hell.\n\n_Hookompose_ allows you to call hook functions in a recompose style, giving you the best of both worlds.\n\nThis is how you use the hook function useState and useEffect:\n\n```js\nconst App = () =\u003e {\n  const [name, setName] = useState('Mary');\n  const [surname, setSurname] = useState('Poppins');\n  useEffect(() =\u003e { document.title = name + ' ' + surname; });\n\n  return (\n    \u003c\u003e\n      Name: \u003cinput value={name} onChange={e =\u003e setName(e.target.value)} /\u003e\n      Surname: \u003cinput value={surname} onChange={e =\u003e setSurname(e.target.value)} /\u003e\n    \u003c/\u003e\n  );\n}\n\nexport default App;\n```\n\nThis is how you use the hookompose function withState and withEffect:\n\n```js\nimport { compose, withState, withEffect } from 'hookompose';\n\nconst App = ({ name, setName, surname, setSurname }) =\u003e\n  \u003c\u003e\n    Name: \u003cinput value={name} onChange={e =\u003e setName(e.target.value)} /\u003e\n    Surname: \u003cinput value={surname} onChange={e =\u003e setSurname(e.target.value)} /\u003e\n  \u003c/\u003e;\n\nexport default compose(\n  withState('name', 'Mary'),\n  withState('surname', 'Poppins'),\n  withEffect(p =\u003e document.title = p.name + ' ' + p.surname)\n)(App);\n```\n\nThe App component is now a stateless, pure presentational component, local states become props. This is exactly the same style as recompose, but using hooks, thus no wrapper hell.\n\nThe _withState_, _withEffect_ are just wrapper functions of the corresponding hook functions (_useState_, _useEffect_). They call the corresponding hook functions and return the result as an object containing new props. The _compose_ function is a HOC, it takes the props of the presentational component, run it through all the enhancer functions and return a new component with the enhanced props.\n\nHookompose provides a list of wrapper functions, one for each hook function, but you can use any function (which takes the current props as input and return an object containing new props) as an enhancer.\n\nThe order of the enhancer functions are important, the new props just added are available to the next enhancer function.\n\n[This post](https://medium.com/@ln613/use-react-hooks-in-recompose-style-50446043eb23) gives a detailed explanation on the implementation of the _compose_ and _enhancer functions_.\n\n## Docs\n\n\n### `withState`\n\n_withState_ takes two arguments, state name (required) and initial value. It calls _useState_ and returns the name/setName pair as object. To simplify the argument list, the name of the setter is automatically generated.\n\n```js\n  withState('surname', 'Poppins') // returns { surname: ..., setSurname: ... }\n```\n\n\n### `withEffect`\n\n_withEffect_ takes four arguments, the _effect_ function (required), the _cleanup_ function, the _dependency_ list and a boolean value _useLayout_ indicating whether to use _useLayoutEffect_ or _useEffect_.\n\nBoth the _effect_ function and the _cleanup_ function takes the current props as input.\n\nThe _dependency_ list is a list of prop names that the effect depends on, the _effect_ function and the _cleanup_ function will be executed when any of the values in the _dependency_ list changes. If not provided, all props of the _effect_ function will be in the _dependency_ list. If an empty array is provided, the effect will only happen after mounting, and cleanup will only happen after unmounting.\n\n```js\n  withEffect(p =\u003e document.title = p.name + ' ' + p.surname) // document.title will be set on every render\n  withEffect(p =\u003e document.title = p.name + ' ' + p.surname, null, ['surname']) // document.title will be set only if surname changes\n  withEffect(p =\u003e document.title = p.name + ' ' + p.surname, null, p =\u003e [p.name, p.surname]) // The dependency list can also be specified as a callback function\n```\n\n### `withLayoutEffect`\n\n```js\n  withLayoutEffect(effect, cleanup, deps)\n```\n\nis the same as\n\n```js\n  withEffect(effect, cleanup, deps, true)\n```\n\nWhich will call _useLayoutEffect_ instead of _useEffect_.\n\n\n### `withEventHandler`\n\nWhen you use the effect hook to attach event handlers to elements, you always have to clean up and remove the event handler. _withEventHandler_ is a helper function that will help you remove the event handler.\n\n_withEventHandler_ takes four arguments, the CSS _selector_ (required) which is used to identify the elements, the _event_ name (required), the _handler_ (required) which takes the props (the event args object will be added to the props as 'event') as input and the _dependency_ list.\n\n```js\n  withEventHandler('#btn2', 'click', p =\u003e p.setCount(p.count + 1))\n```\n\nBased on the design of hooks, this will remove and re-attach the handler on every render. If you want to improve performance and remove the handler only on unmounting, specify an empty dependency list. This will attach the handler on mounting and remove it on unmounting. However, if your handler is using any state from the props, be aware of the stale state/prop issue. If you do the following:\n\n```js\n  withEventHandler('#btn2', 'click', p =\u003e p.setCount(p.count + 1), [])\n```\n\nThe _p.count_ will not be changed after the first render. This is because of the way how javascript closure works. For more information, please refer to [hooks documentation](https://reactjs.org/docs/hooks-faq.html#performance-optimizations).\n\nThere are 3 ways to solve the problem:\n\n1. add _p.count_ to the dependency list:\n\n```js\n  withEventHandler('#btn2', 'click', p =\u003e p.setCount(p.count + 1), ['count'])\n```\n\n2. for local state, use the callback form of the setter:\n\n```js\n  withEventHandler('#btn2', 'click', p =\u003e p.setCount(count =\u003e count + 1), [])\n```\n\n3. use the _useReducer_ hook. See _withReducer_.\n\n\n### `withWindowEventHandler`\n\n_withWindowEventHandler_ allows you to attach event handlers on the window object.\n\n```js\n  withWindowEventHandler('resize', p =\u003e p.setWidth(window.innerWidth), [])\n```\n\nIn this example, using the empty dependency list is safe because the event handler is not using any state from the props (it uses setWidth from the props, but it's a static function, it never changes).\n\n### `withInterval`\n\n_withInterval_ will call the useEffect hook to _setInterval_ and _clearInterval_.\n\n```js\n  withInterval(p =\u003e p.setCount(c =\u003e c + 1), 1000, [])\n```\n\n### `withMemo`\n\n_withMemo_ will call the _useMemo_ hook to remember the result of the enhancer, only re-calculate the result if dependency list changes.\n\n```js\n  compose(\n    withState('name', 'Mary'),\n    withState('surname', 'Poppins'),\n    withMemo(p =\u003e ({\n      fullName: p.name + ' ' + p.surname\n    }), ['name', 'surname'])\n  )(App)\n```\n\nIt serves the same purpose as _withPropsOnChange_ from recompose. You can also use it to achieve the same result as the _useCallback_ hook (see examples under 'withRef' and 'withReducer').\n\n### `withRef`\n\n_withRef_ will call useRef to create a ref to a mutable object (usually an UI element), and attach the ref to the props.\n\n```js\nconst App = ({ inputEl, focusInputEl }) =\u003e\n  \u003c\u003e\n    \u003cinput ref={inputEl} type=\"text\" /\u003e\n    \u003cbutton onClick={focusInputEl}\u003eFocus the input\u003c/button\u003e\n  \u003c\u003e;\n\nexport default compose(\n  withRef('inputEl'),\n  withMemo(p =\u003e ({\n    focusInputEl: () =\u003e p.inputEl.current.focus()\n  }), []),\n)(App);\n```\n\n### `withContext`\n\n_withContext_ will call the _useContext_ hook to enhance the props with the context value. The 2nd argument is the name of the context variable on the props, default value is \"context\".\n\n```js\n// in myContext.js\nexport const MyContext = createContext();\n\n// in contextProvider.js\n\u003cMyContext.Provider value={{ n1: 8, n2: 9 }}\u003e\n  \u003cContextConsumer /\u003e\n\u003c/MyContext.Provider\u003e\n\n// in contextConsumer.js\nconst ContextConsumer = ({ ctx }) =\u003e\n  \u003cdiv\u003eContext Value: {ctx.n2}\u003c/div\u003e;\n\nexport default compose(\n  withContext(MyContext, 'ctx')\n)(ContextConsumer);\n```\n\n### `withReducer`\n\n_withReducer_ will call the _useReducer_ hook to handle complex local states. For use cases of _useReducer_, please refer to the [hooks documentation](https://reactjs.org/docs/hooks-reference.html#usereducer). The 2nd argument of _withReducer_ is the initial state, the 3rd is the name of the state (default value is 'state'), and the 4th is the name of the dispatch function (default value is 'dispatch').\n\n```js\n// in myReducer.js\nexport default (state, action) =\u003e {\n  switch (action.type) {\n    case 'increment':\n      return {count: state.count + 1};\n    case 'decrement':\n      return {count: state.count - 1};\n    default:\n      throw new Error();\n  }\n}\n\n// in App.js\nconst App = ({ state, inc, dec }) =\u003e\n  \u003c\u003e\n    \u003cdiv\u003eCount: {state.count}\u003c/div\u003e\n    \u003cbutton onClick={inc}\u003e+\u003c/button\u003e\n    \u003cbutton onClick={dec}\u003e-\u003c/button\u003e\n  \u003c/\u003e;\n\nexport default compose(\n  withReducer(myReducer, { count: 0 }),\n  withMemo(p =\u003e ({\n    inc: () =\u003e p.dispatch({ type: 'increment' }),\n    dec: () =\u003e p.dispatch({ type: 'decrement' })\n  }), [])\n)(App);\n```\n\n## Custom hooks\n\nSimilar to the purpose of custom hooks, you can combine enhancers to re-use them as a unit.\n\n```js\nconst withWidth = [\n  withState('width', window.innerWidth),\n  withWindowEventHandler('resize', p =\u003e p.setWidth(window.innerWidth), [])\n];\n\nexport default compose(\n  withState('name', 'Mary'),\n  ...withWidth\n)(App);\n```\n\nSince an enhancer is just a function which takes the current props and returns additional props, if you already have a custom hook or any other function with effect, you just need to create a wrapper function that returns the result as additional props.\n\n```js\nconst useFullName = () =\u003e {\n  const [name, setName] = useState('Mary');\n  const [surname, setSurname] = useState('Poppins');\n  return useMemo(() =\u003e name + ' ' + surname, [name, surname]);\n};\n\nexport default compose(\n  p =\u003e ({ fullName: useFullName() })\n)(App);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fln613%2Fhookompose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fln613%2Fhookompose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fln613%2Fhookompose/lists"}