{"id":16349521,"url":"https://github.com/arojunior/react-hooks-composition-proposal","last_synced_at":"2026-04-11T21:39:06.382Z","repository":{"id":43543971,"uuid":"224461097","full_name":"arojunior/react-hooks-composition-proposal","owner":"arojunior","description":"React hooks composition proposal","archived":false,"fork":false,"pushed_at":"2023-01-05T01:49:21.000Z","size":782,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-09T05:41:13.207Z","etag":null,"topics":["compose","hooks","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/arojunior.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-11-27T15:29:23.000Z","updated_at":"2020-05-29T21:52:46.000Z","dependencies_parsed_at":"2023-02-03T04:46:55.863Z","dependency_job_id":null,"html_url":"https://github.com/arojunior/react-hooks-composition-proposal","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/arojunior/react-hooks-composition-proposal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arojunior%2Freact-hooks-composition-proposal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arojunior%2Freact-hooks-composition-proposal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arojunior%2Freact-hooks-composition-proposal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arojunior%2Freact-hooks-composition-proposal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arojunior","download_url":"https://codeload.github.com/arojunior/react-hooks-composition-proposal/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arojunior%2Freact-hooks-composition-proposal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272014462,"owners_count":24858703,"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-25T02:00:12.092Z","response_time":1107,"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":["compose","hooks","react"],"created_at":"2024-10-11T01:00:09.857Z","updated_at":"2026-04-11T21:39:01.334Z","avatar_url":"https://github.com/arojunior.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React hooks composition proposal\n\nI will quote the README from [react-compose-hooks](https://github.com/lucasconstantino/react-compose-hooks) because the motivation is basically the same, I just don't agree 100% with that implementation.\n\n\u003e --\u003e The copied text starts here:\n\n## Motivation\n\n1. **Side-effect:** no one really like them, and within the React ecosystem we've been trying to get rid of them - or at least encapsulate them for good. Hooks seems to go in the other direction, when it encourages people to call a function and expect a dynamic return inside a previously purely functional component. Which leads to...\n2. **Not functional:** I might be completely wrong with this one, but it seems we've just buried some concepts of functional programming when embracing hooks. No more pure functions, which should _always return the same result when called with the same arguments_. Which also leeds to...\n3. **Testability issues:** APIs are certaily to come, but so far we are all sure that using hooks will not encourage testable code at all.\n\nHaving all that said, we have to point the obvious answer to all these problems, which is: we already had these problems with classes. This is true, but now we are making the distinction between logic and presentational components much more subtle. Experienced developers are sure going to keep things separetely enough, but what about newcommers? They were once tempted to use classes everywhere, and the introduction of purely functional components was a good way to teach them to split logic from presentation. The difference between smart/dumb (container/component, whatever) is now way more difficult to grasp.\n\n## Solution\n\nI don't have a final solution. All I know is I've loved the developing experience gains first brought by [recompose](https://github.com/acdlite/recompose)\n\n\u003e --\u003e And ends here.\n\nThose were [Lucas Constatino](https://github.com/lucasconstantino)'s words.\n\n## My two cents\n\nI really love `recompose` as well, but I can agree that is too much abstraction and high order components.\nThat said, I think we can use the best of the two worlds.\n\nThis is a component using `useState` and `useEffect` hooks:\n\n```javascript\n// AppComponent.js\nconst AppComponent = ({ useFoo, useGithub }) =\u003e {\n  const { foo, changeFoo } = useFoo(\"bar\");\n  const { user } = useGithub(\"arojunior\");\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cheader className=\"App-header\"\u003e\n        \u003cimg src={logo} className=\"App-logo\" alt=\"logo\" /\u003e\n        \u003ch2\u003eHello {foo}\u003c/h2\u003e\n        \u003ch2\u003eStart editing to see some magic happen!\u003c/h2\u003e\n        \u003cbutton onClick={() =\u003e changeFoo(\"wooow\")}\u003eChange bar\u003c/button\u003e\n        \u003cdiv\u003e\n          \u003cp\u003e\n            \u003cstrong\u003eName: \u003c/strong\u003e\n            {user.name}\n          \u003c/p\u003e\n        \u003c/div\u003e\n      \u003c/header\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nWhat is the difference so far? There's no implementation inside the Component. It's using custom hooks and receiving it by props.\n\nThe custom hooks:\n\n```javascript\n// AppService.js\nimport { useState, useEffect } from 'react';\n\nexport const useFoo = initialState =\u003e {\n  const [foo, setFoo] = useState(initialState);\n  const changeFoo = value =\u003e {\n    setFoo(value === foo ? initialState : value);\n  };\n  return { foo, changeFoo };\n};\n\nexport const useGithub = username =\u003e {\n  const [user, setUser] = useState({});\n\n  useEffect(() =\u003e {\n    const getUser = async () =\u003e {\n      const githubUser = await fetch(\n        `https://api.github.com/users/${username}`\n      );\n      return githubUser.json();\n    };\n\n    getUser().then(u =\u003e setUser(u));\n  }, [user, username]);\n\n  return { user };\n};\n```\n\nAnd the magic happens here:\n\n```javascript\n// AppContainer.js\nimport { withProps } from './utils/hocFactory';\nimport { useFoo, useGithub } from './AppService';\nimport AppComponent from './AppComponent';\n\nconst AppContainer = withProps({\n  useFoo,\n  useGithub\n})(AppComponent);\n\nexport default AppContainer;\n```\n\nJust one HOC and all of the responsibilities are clear.\nWith this kind of implementation, we can easily test the `AppComponent.js` as a pure component:\n\n```javascript\n// AppComponent.test.js\ndescribe(\"AppComponent\", () =\u003e {\n  test(\"should match snapshot\", () =\u003e {\n    const useFoo = jest.fn(() =\u003e ({}));\n    const useGithub = jest.fn(() =\u003e ({ user: {} }));\n\n    const tree = renderer\n      .create(\u003cAppComponent useFoo={useFoo} useGithub={useGithub} /\u003e)\n      .toJSON();\n\n    expect(tree).toMatchSnapshot();\n  });\n});\n```\n\nWe can also test the behavior (hooks) separated:\n\n```javascript\n// AppService.test.js\ndescribe(\"AppService\", () =\u003e {\n  describe(\"useFoo\", () =\u003e {\n    test(\"should render the correct initialState\", () =\u003e {\n      const { result } = renderHook(() =\u003e useFoo(\"bar\"));\n      expect(result.current.foo).toBe(\"bar\");\n    });\n\n    test(\"should change foo value\", () =\u003e {\n      const { result } = renderHook(() =\u003e useFoo(\"bar\"));\n      act(() =\u003e {\n        result.current.changeFoo(\"woow\");\n      });\n      expect(result.current.foo).toBe(\"woow\");\n    });\n\n    test(\"should change foo value to initialState when new value is equals to previous\", () =\u003e {\n      const { result } = renderHook(() =\u003e useFoo(\"bar\"));\n      act(() =\u003e {\n        result.current.changeFoo(\"woow\");\n      });\n      act(() =\u003e {\n        result.current.changeFoo(\"woow\");\n      });\n      expect(result.current.foo).toBe(\"bar\");\n    });\n  });\n});\n```\n\nAnd then we can test the two things together, the presentational component and the behavior:\n\n```javascript\n// AppContainer.test.js\ndescribe(\"AppContainer\", () =\u003e {\n  beforeAll(() =\u003e {\n    const fakeUserResponse = { name: \"Junior Oliveira\" };\n\n    jest.spyOn(window, \"fetch\").mockImplementation(() =\u003e {\n      return Promise.resolve({\n        json: () =\u003e Promise.resolve(fakeUserResponse)\n      });\n    });\n  })\n\n  test(\"Render with useGithub hook and its initial state\", async () =\u003e {\n    const { getByText } = render(\u003cAppContainer /\u003e);\n    await wait(() =\u003e {\n      expect(getByText(/Junior Oliveira/i)).toBeInTheDocument();\n    })\n  });\n\n  test(\"Render with useFoo hook and its initial state\", async () =\u003e {\n    const { getByText } = render(\u003cAppContainer /\u003e);\n    await wait(() =\u003e {\n      expect(getByText(/Hello bar/i)).toBeInTheDocument();\n    })\n  });\n});\n\n```\n\nWhat do you think about it? Feel free to open issues and discuss about this approach.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farojunior%2Freact-hooks-composition-proposal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farojunior%2Freact-hooks-composition-proposal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farojunior%2Freact-hooks-composition-proposal/lists"}