{"id":20595301,"url":"https://github.com/myty/clean-functional-react","last_synced_at":"2026-04-20T00:04:13.596Z","repository":{"id":51330301,"uuid":"358302246","full_name":"myty/clean-functional-react","owner":"myty","description":"Exploration into what makes good, clean functional react hooks and components.","archived":false,"fork":false,"pushed_at":"2021-07-09T18:42:35.000Z","size":1002,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-06T14:54:36.595Z","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/myty.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":"2021-04-15T15:12:21.000Z","updated_at":"2021-05-28T21:30:35.000Z","dependencies_parsed_at":"2022-09-05T20:11:09.009Z","dependency_job_id":null,"html_url":"https://github.com/myty/clean-functional-react","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/myty/clean-functional-react","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myty%2Fclean-functional-react","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myty%2Fclean-functional-react/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myty%2Fclean-functional-react/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myty%2Fclean-functional-react/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/myty","download_url":"https://codeload.github.com/myty/clean-functional-react/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myty%2Fclean-functional-react/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284902884,"owners_count":27081964,"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-11-17T02:00:06.431Z","response_time":55,"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-16T08:12:39.018Z","updated_at":"2025-11-17T15:07:21.777Z","avatar_url":"https://github.com/myty.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Clean Functional React\n\nReact hooks were first introduced in October 2018 and released with React v16.8.  Since then, we've been enamored with their simplicity and composability. Anyone that uses hooks extensively, quickly finds out that the components that consume them, get complicated and messy...real fast. Trying to do hooks in any large scale codebase becomes even more difficult, not to mention, testing is most likely an after thought. My hope, is to bring some clarity and structure to our custom react hooks and the components that use them.  The end goal is that our hooks are clean, organized, understandable, and dare I say testable. 😲\n\n## Table of Contents\n\n- [Clean Functional React](#clean-functional-react)\n  - [Table of Contents](#table-of-contents)\n    - [Reactive Data Flow Pattern](#reactive-data-flow-pattern)\n      - [React Hooks](#react-hooks)\n        - [useState](#usestate)\n        - [useContext](#usecontext)\n        - [useRef](#useref)\n        - [useMemo](#usememo)\n        - [useCallback](#usecallback)\n        - [useReducer](#usereducer)\n        - [useEffect](#useeffect)\n      - [Clear Boundaries](#clear-boundaries)\n    - [Anti-Patterns](#anti-patterns)\n    - [Testability](#testability)\n\n### Reactive Data Flow Pattern\n\nOne of the first difficulties encountered when working with hooks is trying to understand why a component rendered and re-rendered. It's why hooks such as [why-did-you-render](https://github.com/welldone-software/why-did-you-render) and [useWhyDidYouUpdate](https://usehooks.com/useWhyDidYouUpdate/) exist.\n\n```text\n// TODO\n```\n\nProperties returned from a hook should either be a data field or a command/action field.  It either gets something or does something. It should never be both. This closely follows the [command query separation principle](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation).\n\n![react hooks data flow diagram](./docs/assets/clean-react-hook.drawio.svg)\n\n- ❌ Avoid async functions that return data. _The example below is not only displaying the returned data, but also handling state and data flow logic. It is not doing a good job at separating concerns._\n\n    ```typescriptreact\n    const Component = (props: ComponentProps) =\u003e {\n        const { id } = props;\n        const [ error, setError ] = useState();\n        const [ loading, setLoading ] = useState();\n        const [ value, setValue ] = useState();\n        const { getValue } = useSomeHook({ id });\n\n        const load = useCallback(async () =\u003e {\n            setLoading(true);\n\n            try {\n                const value = await getValue();\n                setState(value);\n            } catch (error) {\n                setError(error);\n            }\n\n            setLoading(false);\n        }, [getValue]);\n\n        return (\n            \u003cdiv\u003e\n                \u003cbutton onClick={load}\u003eLoad\u003c/button\u003e\n                \u003cdiv\u003eValue: {value}\u003c/div\u003e\n                \u003cdiv\u003eLoading: {loading}\u003c/div\u003e\n                \u003cdiv\u003eError: {error}\u003c/div\u003e\n            \u003c/div\u003e\n        );\n    }\n    ```\n\n- ✅ Prefer synchronous fire-and-forget action commands. Any returned data should flow back through the properties of the hook. _Components that consume a hook like this are easier to read and do a better job at separating concerns.  The component is only concerned with displaying the data returned from the hook rather than also handling state and data control logic._\n\n    ```typescriptreact\n    const Component = (props: ComponentProps) =\u003e {\n        const { id } = props;\n        const { value, error, loading, load } = useSomeHook({ id });\n\n        return (\n            \u003cdiv\u003e\n                \u003cbutton onClick={load}\u003eLoad\u003c/button\u003e\n                \u003cdiv\u003eValue: {value}\u003c/div\u003e\n                \u003cdiv\u003eLoading: {loading}\u003c/div\u003e\n                \u003cdiv\u003eError: {error}\u003c/div\u003e\n            \u003c/div\u003e\n        );\n    }\n    ```\n\n#### React Hooks\n\nThe built-in React hooks generally follow the command/query separation principle in two ways.\n\n1. The hook itself either returns something or does something.\n2. If the hook does return properties, they are either a command(action) or a query(data).\n\n##### useState\n\n```typescriptreact\nconst [state, setState] = useState\u003cstring\u003e();\nstate    // [data]    -\u003e string\nsetState // [command] -\u003e (state: value) =\u003e void\n         // [command] -\u003e ((previousState: value) =\u003e string) =\u003e void\n```\n\n##### useContext\n\n```typescriptreact\nconst value = useContext(MyContext);\nvalue // [data] -\u003e any\n```\n\n##### useRef\n\n```typescriptreact\nconst ref = useRef\u003cstring\u003e();\nref // [data] -\u003e { current: string }\n```\n\n##### useMemo\n\n```typescriptreact\nconst memoizedValue = useMemo(() =\u003e computeExpensiveValue(a, b), [a, b]);\nmemoizedValue // [data] -\u003e any\n```\n\n##### useCallback\n\n```typescriptreact\nconst memoizedCallback = useCallback(\n  () =\u003e {\n    doSomething(a, b);\n  },\n  [a, b],\n);\nmemoizedCallback // [command] -\u003e () =\u003e any\n```\n\n##### useReducer\n\nAt first glance it appears that `useReducer` is doing something as well as also returning something, but in reality it is returning an action and data with zero side-effects.  The action is what will force the data to change and return.\n\n```typescriptreact\nconst { state, dispatcher } = useReducer (\n    (state, action) =\u003e state,\n    { initialState }\n);\nstate    // [data]    -\u003e any\ndispatch // [command] -\u003e (action) =\u003e void\n```\n\n##### useEffect\n\nuseEffect is a special case and is actually an illustration where the hook itself is either a command or a query. Most hooks are returning something, but the `useEffect` hook is in fact __doing__ something.\n\n```typescriptreact\nuseEffect(() =\u003e {\n  if (a != null \u0026\u0026 b != null) {\n    doSomething();\n  }\n}, [a, b]);\n```\n\n#### Clear Boundaries\n\n- Eliminate confusion of where a change originates or why a component re-renders\n\n```markdown\nTODO\n```\n\n### Anti-Patterns\n\n```markdown\nTODO\n```\n\n### Testability\n\n- Starting development of a component or hook with a TDD approach\n  - Separate concerns\n  - Enforce clean code guidelines\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyty%2Fclean-functional-react","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmyty%2Fclean-functional-react","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyty%2Fclean-functional-react/lists"}