{"id":13422488,"url":"https://github.com/kapolos/react-godfather","last_synced_at":"2026-01-12T11:24:58.780Z","repository":{"id":52053387,"uuid":"346402820","full_name":"kapolos/react-godfather","owner":"kapolos","description":"\"Look ma, no Hooks!\"","archived":false,"fork":false,"pushed_at":"2021-05-07T22:42:49.000Z","size":321,"stargazers_count":28,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-08-09T14:29:08.950Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/kapolos.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-03-10T15:31:46.000Z","updated_at":"2025-07-12T18:44:53.000Z","dependencies_parsed_at":"2022-08-30T23:50:15.197Z","dependency_job_id":null,"html_url":"https://github.com/kapolos/react-godfather","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/kapolos/react-godfather","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapolos%2Freact-godfather","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapolos%2Freact-godfather/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapolos%2Freact-godfather/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapolos%2Freact-godfather/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kapolos","download_url":"https://codeload.github.com/kapolos/react-godfather/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapolos%2Freact-godfather/sbom","scorecard":{"id":549757,"data":{"date":"2025-08-11","repo":{"name":"github.com/kapolos/react-godfather","commit":"e0a3a4c6ef24b2cfaa26eb59a05bc7442f0dcabe"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/16 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 11 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T10:28:38.977Z","repository_id":52053387,"created_at":"2025-08-20T10:28:38.977Z","updated_at":"2025-08-20T10:28:38.977Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T10:58:46.209Z","status":"ssl_error","status_checked_at":"2026-01-12T10:58:42.742Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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-07-30T23:00:46.033Z","updated_at":"2026-01-12T11:24:58.738Z","avatar_url":"https://github.com/kapolos.png","language":"JavaScript","funding_links":[],"categories":["Code Design"],"sub_categories":["Miscellaneous"],"readme":"# React-Godfather\n\u003e \"Look ma, no Hooks!\"\n\n**React-Godfather aims to explore an alternative mental model for function components.**\nIt adds a thin layer between your shiny components and React, quietly instrumenting things behind the scenes - \nand it wants to make you an offer you can't refuse.\n\nHere is what you get:\n\n* A very natural, top-down local state management which does **not** feel like a DSL...\n* ... and plays great with your existing code - you can progressively adopt it in your code-bases.\n* Fully Asynchronous components to `await` all you want, even within the render function...\n* ...that also supports async generators, for all your `yield`ing extravaganzas.\n* Code-splitting without wrapping.\n* Wings for your junior team colleagues.\n\n## Index\n\n* [How does a component look like?](#can-you-tell-what-this-does)\n* [The basic characteristics of a component](#lets-unpack-it)\n* [Structure, State \u0026 Render](#structure-state-and-render)\n* Understanding Rendering  \n    * [Explicit re-render](#tick-toc)\n    * [Implicit re-rendering](#implicit-tick)\n* Component Props\n    * [A tale of two cities](#what-about-props)\n    * [Gotcha!](#heres-a-gotcha-props-example)\n    * [Getting it right](#heres-the-prop-er-pun-intended-version)\n* [Nesting Components](#nesting-components) \n    * [Will my kitchen blow up if I overdo it?](#would-the-known-universe-collapse-)\n    * [But does it run Crysis?](#whoa-whoa-wait-a-minute)\n* [Events tracked by default](#le-default-event-list)\n* [Async / Promises](#async--promises)\n    * [Elementary, my dear Watson](#its-pretty-straightforward)\n    * [CodeSplitting, for free](#code-splitting)\n    * [A simple pattern](#what-about-an-error-message-on-failure-to-import)\n    * [This won't work](#what-about-a-message-while-it-awaits)\n    * [But it would work like this](#we-can-do-it-in-a-cleaner-way)\n    * [And it definitely works great like this!](#naturally-we-can-do-much-better)\n    * [How to move explicit ticks out of view](#any-alternative-style-for-thentick)\n* [Async Generators](#async-generators)\n* [Component Cleanup](#cleanup-function) \n* [Code Examples](#code-examples)\n    * [StoryBook](#storybook)\n    * [CodeSandbox](#codesandbox)\n* [What is under the hood?](#how-does-this-magic-work)\n* [Usage \u0026 understanding the configuration](#usage--understanding-the-configuration)\n* [FAQ](#faq)\n    * [But why?](#but-why)\n    * [What about speed? Is it perhaps slow?](#what-about-speed-isnt-checking-for-deep-equality-slow)\n    * [How do I use the Context API?](#)\n* [License](#license)    \n\n\n## Preamble\n\nReact-Godfather is straightforward, easy but different. \n\nConsidering that it's a newborn idea which is very different from our usual practice, \ndryly listing the differences and features does not seem like the most helpful way to go about it.\nInstead, I've opted to construct this Readme with examples and discuss them in a step by step way. \nI have tried to keep the narrative style very informal, as if we were drinking coffee together and chatting. \n\nThis way, I hope that everything will make perfect sense in the end, without the reader expanding any serious effort. \nSo please [bear](https://imgur.com/t/bear/SBhKxUd) with me.\n\nThere are links to live playgrounds for each example. \nFurthermore, this repo contains Storybook examples for you to examine and play with.\nTo use the Storybook, clone the repo, npm/yarn install and yarn start.\n\nAlright, let's start!\n\n## Can you tell what this does?\n\n*(You can play with the example [here](https://codesandbox.io/s/react-godfather-docs-demo-1-dco9i) - \npress the button a few times for fun)*\n\n```javascript\nconst App = () =\u003e {\n  let remoteData, error\n\n  const fetchData = async () =\u003e {\n    try {\n      // `dummyResponse` mocks a call that fails randomly\n      remoteData = await dummyResponse()\n      error = false\n    } catch (e) {\n      error = true\n    }\n  }\n\n  return ({ tick }) =\u003e {\n    if (error) {\n      return (\n        \u003cdiv\u003e\n          \u003cp\u003eSomething went wrong. Ray-id: {Math.random()}\u003c/p\u003e\n          \u003cbutton onClick={fetchData}\u003eRe-try\u003c/button\u003e\n        \u003c/div\u003e\n      )\n    }\n\n    if (!remoteData) {\n      fetchData().then(tick)\n\n      return \u003cdiv\u003eLoading...\u003c/div\u003e\n    }\n\n    return (\n      \u003cdiv\u003e\n        \u003cp\u003e\n          Received: {remoteData}. Ray-id: {Math.random()}\n        \u003c/p\u003e\n        \u003cbutton onClick={fetchData}\u003eFetch again\u003c/button\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n\nexport default toC(App, ['onClick'])\n```\n\nOf course you can! That's one of `react-godfather`'s main goals. \nThe top-down \"reading\" of the code makes it natural to reason with it.\n\n\n## Let's unpack it.\n\nReact-godfather components have some differences from the standard React function components.\n\n\u003cimg src=\"https://github.com/kapolos/react-godfather/blob/main/docs/screenshots/scribble1-unpacked.jpg?raw=true\" width=\"560\" alt=\"scribble1\"\u003e\n\nIn this specific example, `remoteData` and `error` hold our local state. They are defined (along with the `fetchData`\nfunction) above the component's `return` call. Everything in this section of the component is executed once and stays\nalong for the whole lifetime of the component - in other words, variables here survive across re-renders.\n\n### Structure, State and Render\n\nAs we've seen, the component consists of 2 sections. The first one is everything before the return statement. \nThis part **only executes once** and the state here is kept **across re-renders**.\nIn the old React Class terms, we can think of it as the equivalent of field declaration, `state` and the constructor, all bundled together.\n\nThe second section lies within the component's return function. \nNotice that we return a function and not directly JSX (which it itself is a function, but still, you get the idea).\n\nThis function executes **on every render** and **has access to everything declared on the first section**. \nIt can be `async` if we want (we'll see an example later on). \nIf we again think in terms of the old React Class, this would be the `render` function.\n\nTherefore, **the role of the first section is to act as the mutable state container for our \"render\" function**.\n\n### Tick ToC\n\nA React-godfather component needs something to drive it and eventually output a standard React component. \nAfter all, we are still relying on React (which is awesome, btw), so we need to play ball with it. \nFor this, a wrapper is used, which I have _\\*cough\\*_ imaginatively _\\*cough\\*_ named \"to Component\",\nor just `toC` for friends.\n\nToC adds some extras to the component's `props`. That's where `tick` comes from.\nTick is responsible to advance the state (detailed explanation follows - surprise - later on) and materialize the changes.\nBasically, `tick` advances the internal state of `toC` and re-renders the component.\n\n### Implicit Tick\n\nBut wait! If I have to `tick` to advance the state, why did it just work when clicking the button? \n\nNaturally, it's because laziness precedes reward.\n\nWe don't really want to be manually typing `tick` all over the place, especially when it comes to \nhandling `onX` events which is a very common thing in the daily coding life.\nTherefore, `toC` can be configured to detect the `onX` events and advance its state on its own. Yay!\n\n(In case you're worried that typing that `['onClick']` is too labor intensive, we're not done lazying yet - \nwait till we get there.)\n\n## What about props?\n\nWe talked about react-godfather components having two parts. One that executes on initialization,\nand the other the executes on every render.\n\nProps get passed in both places, like this:\n\n```js\nconst Foo = toC((initialProps) =\u003e {\n  let { bar } = initialProps\n  \n  return (props) =\u003e {\n    bar = props.bar\n    \n    return \u003cdiv\u003e{bar}\u003c/div\u003e\n  }\n})\n```\n\nThe `props` hold the up-to-date value for the render. \nBut `initialProps` always hold the value as they were at the time of initialization!\n\n### Here's a gotcha props example\n\n(**[playground link](https://codesandbox.io/s/react-godfather-docs-demo-2a-f7947)**)\n\n```js\n// Just a form with a controllable text input component\nconst InputForm = toC(() =\u003e {\n  let value = \"foo\";\n\n  const handleOnChange = (e) =\u003e {\n    value = e.target.value;\n  };\n\n  return () =\u003e (\n          \u003cform spellCheck={false}\u003e\n            \u003cdiv style={{ color: \"blue\" }}\u003e{value}\u003c/div\u003e\n            \u003cInputWithInitialPropsValue value={value} handleOnChange={handleOnChange} /\u003e\n          \u003c/form\u003e\n  );\n}, [\"onChange\"]);\n\n// Frodo was here\nconst InputWithInitialPropsValue = toC(({ value, handleOnChange }) =\u003e {\n  return () =\u003e (\n          \u003cinput\n                  type='text'\n                  className='input'\n                  value={value}\n                  onChange={handleOnChange}\n          /\u003e\n  )\n}, ['onChange'])\n```\n\nWhat's the problem here? As we type keys, the `handleOnChange` function always concatenates the key with `foo`. \nSo if we press `t`, we get `food` and it we then press `s` we are still left with a `food` instead of `foods` because the `value` that\n`InputWithInitialPropsValue` sees on every render is always `foo` - the value that at the time of its initialization.\n\n### Here's the prop-er (pun intended) version\n\n**[playground link](https://codesandbox.io/s/react-godfather-docs-demo-2b-lzgs0)**\n\n```js\nconst InputFixed = toC(({ handleOnChange }) =\u003e {\n  return ({ value }) =\u003e (\n          \u003cinput\n                  type='text'\n                  className='input'\n                  value={value}\n                  onChange={handleOnChange}\n          /\u003e\n  )\n}, ['onChange'])\n```\n\nThere we go! The input behaves properly as we type, because we ask for the updated `value` on each render.\nThe rule of thumb is \"when in doubt, use the render props\". Or just always use the render props anyway.\n\n## Nesting components\n\nAnother thing of notice in the form above was that we had two react-godfather components, one nested into the other. \nLet us revisit this with the following - very contrived - example. \nThis is a voting booth with two buttons, and you may vote either yes or no.\nAnd because we don't care enough in the context of the example, \nthere's no way to cast your vote, so you just end up playing with the buttons. \nBut rejoice, for you can press the buttons as many times as you like and it will happily keep track of your madness\nduring this ...regression hypnosis session. But I digress...\n\nThe key here is that we have 2 react-godfather components (one nested in the other) and \nwe want to see another aspect of how they interact.\n\n```js\n// This Button keeps track on how many times it was hit in its local state\nconst Button = toC(({ label, submit }) =\u003e {\n  let hits = 0\n\n  const handleClick = () =\u003e {\n    hits++\n    submit(label)\n  }\n\n  return ({ vote }) =\u003e (\n    \u003cdiv\u003e\n      \u003cp\u003eYou've hit {label} {hits} times\u003c/p\u003e\n      \u003cbutton\n        onClick={handleClick}\n        disabled={vote === label}\n      \u003e{label}\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}, [])\n\n// The Booth does not keep track of the button hits, only the value of the vote\nconst Booth = toC(() =\u003e {\n  let vote\n\n  const handleButton = label =\u003e {\n    vote = label\n  }\n\n  return () =\u003e (\n    \u003cdiv\u003e\n      \u003cp\u003eCurrent vote is: {vote}\u003c/p\u003e\n\n      \u003cButton label='yes' submit={handleButton} vote={vote} /\u003e\n      \u003cButton label='no' submit={handleButton} vote={vote} /\u003e\n    \u003c/div\u003e\n  )\n}, ['onClick'])\n```\n\nThe `Booth` component passes 3 things to the `Button` component:\n* `label`: yes or no\n* `vote`: the current vote: yes, no, null\n* `handleButton`: the imaginatively named function to update `vote`'s value to whichever button you clicked.\n\nThe `Button` component counts the times you've clicked it in the `hits` local state variable. \nIt gets disabled if the current vote is the same as the button's.\n\n**[vote here](https://codesandbox.io/s/react-godfather-docs-demo-3-ggefk)\n\n### Did we forget something?\n\nWait, why is ['onClick'] missing from the button's `toC` parameters?\n\nWe talked about how `toC` can do work for us and detect `onX` events and update its state. \nSo how come `Button` works despite us telling it to disregard monitoring for events?\n\nThat's because of the `handleButton` function. Remember that it executes on the scope of `Booth`, \nhence it changes `Booth`'s state.\n\nOk, `Booth`'s state has changed, but what triggers **its** rerender? \nThe `['onClick']` we have on its `toC` instantiation (last line in the code snippet above).\n\nRecall that DOM (and React's synthetic) events bubble UPwards in the hierarchy tree! We will cover this in detail in \nthe \"What is under the hood?\" section but for now the important thing is that the click event bubbled \nfrom `Button` into `Booth`. And `Booth` is configured to rerender when an ['onClick'] happens.\n\nSince `Booth` re-renders and `vote` has changed, the `Button` components re-renders as well. \nThat's because `react-godfather` **components rerender when their props change**.\n\n### Would the known Universe collapse ...\n...in case we added `['onClick']` on `Button`?\n\nNope, no problem at all. You'll just get an extra re-render of that `Button` instance. That's all.\n\n### Whoa, whoa, wait a minute! \n\n**Q:**\n\nSay I have 50 react-godfather components in a deeply nested way, and the one at the very top is set to react `onClick`. \nAnd suppose the one at the bottom emits a click event,\nbut that event doesn't really matter for the state of the components at the top. \nWill the whole sub-tree still get re-rendered?\n\n**A:**\n\nWell, we could go on  a tangent about React being fast and memoization and stuff,\nbut I bet it still feels a bit uncomfortable, no? \n\n**No worries!** You can optimize this away whenever you feel like it.\n\n`toC` accepts a third parameter, which is a configuration object. It has a property called `stopPropagation`,\nwhich does exactly what you think it does.\n\nSo in our contrived voting booth example, if we suppose that `Booth` is itself nested in other components that \nrespond to `['onClick']` but have no logical need to update their state whenever `Button`'s ... button gets clicked,\nwe can adjust `Booth`'s `toC` like this:\n\n```js\n}, ['onClick'], { stopPropagation: true })\n```\n\nThis nicely brings us to the next important thing we want to know about...\n\n## Le default event list\n\n`toC`'s default event list is `['onClick']`. All those `['onClick']` typed above? Superfluous.\n\nTo instruct `toC` to blissfully avoid reacting on any event, we pass `[]`.\n\n## Async / Promises\n\n`toC`'s return function (the thing that gets rendered) can also be a promise. Let's see some examples.\n\n### It's pretty straightforward\n\n```js\nconst SearchResults = toC(() =\u003e {\n  let data\n  \n  return async ({ keyword, onCompleted }) =\u003e {\n    data = await fetchResults(keyword)\n    onCompleted()\n    \n    return data.map(/* ... */)\n  }\n})\n```\n\nOr with promises:\n\n```js\nconst SearchResults = toC(() =\u003e { \n  return ({ keyword, onCompleted }) =\u003e {   \n    return fetchResults(keyword)\n      .then(data =\u003e data.map(/* ... */))\n      .then(onCompleted)\n  }\n})\n```\n\n## Code splitting\n\n*(In case you'd like a refresher: https://reactjs.org/docs/code-splitting.html#import)*\n\n### This is perfectly valid\n\n```js\n// foo.js\nexport default function Foo() {\n  return \u003cdiv\u003eFoo!\u003c/div\u003e\n}\n```\n\n```js\n// bar.js\nconst Bar = toC(() =\u003e {\n  return async () =\u003e {\n    const Foo = (await import('./foo.js')).default\n\n    return \u003cFoo /\u003e\n  }\n})\n```\n\nAs an aside, that `.default` after `await` is there because we're not using named exports in this example.\n\nLet's pause for a few moments and let the beautiful simplicity of this, gently sink in.\n\n#### What about an error message on failure to import?\n\n```js\nconst Bar = toC(() =\u003e {\n  return async () =\u003e {\n    try {\n      const Foo = (await import('./foo.js')).default\n\n      return \u003cFoo /\u003e\n    } catch (e) {\n      return \u003cdiv\u003eFailed to load.\u003c/div\u003e\n    }\n  }\n})\n```\n\n#### What about a message while it awaits?\n\nWith just what we've seen so far, this could be problematic if we go with `await`, because how are we going to trigger a rerender. We'll view the solution later on but let's verify the issue now:\n\n```js\nconst Bar = toC(() =\u003e {\n  let Foo\n  \n  return async () =\u003e {\n    if (!Foo) {\n      return \u003cdiv\u003eLoading...\u003c/div\u003e\n    } \n    \n    // Execution will never reach here\n    \n    try {\n      Foo = (await import('./foo.js')).default\n\n      return \u003cFoo /\u003e\n    } catch (e) {\n      return \u003cdiv\u003eFailed to load.\u003c/div\u003e\n    }\n  }\n})\n```\n\nWe can do code-splitting without using `await` and instead do the same pattern as the very first example.\nThis doesn't utilize `toC`'s ability to return a promise though.\n\n```js\nconst Bar = toC(() =\u003e {\n  let Foo\n  let error\n\n  return () =\u003e {\n    if (error) {\n      return \u003cdiv\u003eWhooops, I did it again.\u003c/div\u003e\n    }\n\n    if (!Foo) {\n      import('./foo.js')\n        .then(obj =\u003e {\n          Foo = obj.default\n        })        \n        .catch(e =\u003e {\n          error = e\n        })\n        .finally(tick)\n\n      return \u003cdiv\u003eLoading\u003c/div\u003e\n    }\n\n    return \u003cFoo /\u003e\n  }\n})\n```\n\n#### We can do it in a cleaner way\n\n```js\nconst Bar = toC(({ tick }) =\u003e {\n  let Foo\n  let error\n\n  import('./foo.js')\n    .then(obj =\u003e {\n      Foo = obj.default\n    })\n    .catch(e =\u003e {\n      error = e\n    })\n    .finally(tick)\n\n  return () =\u003e {\n    if (error) {\n      return \u003cdiv\u003eWhooops, I did it again.\u003c/div\u003e\n    }\n\n    if (!Foo) {\n      return \u003cdiv\u003eLoading\u003c/div\u003e\n    }\n\n    return \u003cFoo /\u003e\n  }\n})\n```\n\nThat import statement is on the component part that only runs once. This way, the render part of the component\nstays clean and straightforward.\n\n### Naturally, we can do much better!\n\n**C'mon Godfather, I want to do this inside the render function!**\n\nI knew you'd ask, so here we go:\n\n```javascript\nconst Unyielding = toC(({ withTick }) =\u003e {\n  let Foo\n  \n  // Notice that extra `function *` there\n  return async function * () {\n    try {\n      import('./foo.js')\n              .then(C =\u003e { Foo = C.default })\n              .then(() =\u003e delay(1000))\n              .then(tick)\n    } catch (e) {\n      return \u003cdiv\u003eOh dear...\u003c/div\u003e\n    }\n\n    yield \u003cdiv\u003eFetching...\u003c/div\u003e\n\n    return \u003cFoo/\u003e\n  }\n}, [])\n```\n\nYou don't have to be Italian to enjoy good pizza. \nAnd now you don't have to know about generators to `yield` JSX.\n\n*(Play with these variations [here](https://codesandbox.io/s/react-godfather-docs-demo-4-06pc0))*\n\n### Any alternative style for `.then(tick)`?\n\nOf course! Enter `withTick`. This is a wrapper function that does that `.then(tick)` for you.\n\nConsider the following example:\n\n```javascript\nconst Example = toC(({ withTick }) =\u003e {\n  let data = null\n\n  // Function wrapped `withTick`\n  const getMyData = withTick(async () =\u003e {\n    await delay(1200)\n\n    data = '\"But, for my own part, it was Greek to me.\"'\n  })\n\n  return function * () {\n    getMyData() // Will `tick` on its own after it completes\n\n    yield \u003cdiv\u003eFetching...\u003c/div\u003e\n\n    getMyData() // Will `tick` on its own after it completes\n\n    yield \u003cdiv\u003eFetching some more...\u003c/div\u003e\n\n    return (\n      \u003cdiv\u003e{data}\u003c/div\u003e\n    )\n  }\n}, [])\n```\n\nBy wrapping your functions `withTick`, the code inside the render function becomes a bit cleaner.\n\n## Async generators\n\nThe render function of a `react-godfather` function component can be:\n* A function\n* An async function (i.e. a function that returns a promise)\n* A generator function (i.e. a function that returns a Generator object)\n* An async generator (i.e. a function that promises to return a Generator object)\n\nGenerator functions (and their async variants) are a really powerful Javascript feature, since its debut back in 2015. \nTheir only problem is that they are kinda low-level with an awkward syntax. This made them get pushed in the collective \nbackground and eventually paved the way for async/await in 2017.\n\nSo why do we care? Well, generator functions can be \"paused\" and \"resumed\" \n(like in a debugger but without stopping the whole app - just the function execution itself). \n\nThis allows us to do interesting things, avoiding some boilerplate code. And since that's the case, `react-godfather`\nsupports them seamlessly. Just have the component return `function * {}` instead of `() =\u003e {}` \n(and `async function * () {}` instead of `async () =\u003e {}`). Then you can simply `yield` to your heart's content.\n\nNeed a refresher on the concept of Generators? I suggest [this](https://javascript.info/generators-iterators) resource.\n\n*(play with some `yield`s [here](https://codesandbox.io/s/react-godfather-docs-demo-4-06pc0))*\n\n## Cleanup function\n\nSometimes, we need to do some cleanup on unmount because of side effects we've introduced (maybe we've instantiated a \nlibrary outside the React tree or perhaps we need to unsubscribe from an event).\nFor this, we want the equivalent of `componentWillUnmount` or Hooks' `useEffect(() =\u003e cleanup, [])`.\n\nThe structure of react-godfather is such that the component the developer actually writes is a child component to the\ninner \"engine\" component. \nBut the cleanup function needs to be defined in the dev-written component. Therefore, we need to pass a function from \nthe child (the dev-written component) to the parent (the \"engine\" that drives the react-godfather component).\n\nFor this, we introduce another `props` parameter, `onUnmount`. This is a function that we need to call from our \nreact-godfather component. Godfather will remember to call upon it (*\"and that day may never come\"*) when \nReact decides to kill our component.\n\nConsider this example:\n\n```javascript\nconst WithCleanup = toC(({ onUnmount }) =\u003e {\n  const topic = 'foo'\n  let subscribed = false\n\n  const handleClick = () =\u003e {\n    MockAPI.subscribe(topic)\n    subscribed = true\n  }\n\n  // Will execute when React unmounts the component\n  // The provided function has access to the component's state\n  onUnmount(() =\u003e {\n    if (subscribed) {\n      MockAPI.unsubscribe(topic)\n    }\n  })\n\n  return () =\u003e {\n    return (\n      \u003cdiv\u003e\n        \u003cbutton onClick={handleClick}\u003e\n          Subscribe\n        \u003c/button\u003e\n      \u003c/div\u003e\n    )\n  }\n})\n```\n\n`onUnmount` is passed in the component as a prop. It is a function that takes as a parameter the function we want to\nhave executed for cleanup. We provide it with a closure and Godfather will make sure to execute it when the component\nis to be unmounted by React. The closure naturally has access to the component's state.\n\nThis example is on the StoryBook. To test it open the web inspector, select the story, press subscribe and then\npick a different story.\n\n## How does this magic work?\n\nTL;DR: Generators + Event bubbling.\n\nA detailed explanation of how react-godfather works is coming up soon in a blog post.\n\nI will update the docs with a link here once it's up. `Watch` the repo to get notified or send me a hi at \n`react-godfather@kapolos.com` and I'll email you the link once it's up.\n\n## Code Examples\n\n### StoryBook\n\nThis repo contains a number of examples in the format of StoryBook. \nTo access, clone the repo, `npm install` and `npm start`.\n\nThe StoryBook in this repo currently contains the following:\n\n* Demo\n  * Todo List App, with filtering and refetch\n* Examples\n  * Multiplication buttons\n  * Voting Booth  \n  * Form Input  \n  * Props  \n  * Code-splitting  \n  * Cleanup\n* Gotchas    \n  * Initial Props\n* Async generators    \n  * Yield, `withTick`\n  * Async, Yield, `withTick`\n* Helpers    \n  * Wait\n\n### CodeSandbox\n\nFor ease of access \u0026 play, some examples are also provided in CodeSandbox.\n\nThese are the currently available ones:\n\n* [Todo List App](https://codesandbox.io/s/react-godfather-todo-app-ro8e3)\n* Documentation examples\n  * [Example 1](https://codesandbox.io/s/react-godfather-docs-demo-1-dco9i)\n  * [Example 2a](https://codesandbox.io/s/react-godfather-docs-demo-2a-f7947)\n  * [Example 2b](https://codesandbox.io/s/react-godfather-docs-demo-2b-lzgs0)\n  * [Example 3](https://codesandbox.io/s/react-godfather-docs-demo-3-ggefk)\n  * [Example 4](https://codesandbox.io/s/react-godfather-docs-demo-4-06pc0)\n\n## Usage \u0026 understanding the configuration\n\n### Install\n\n`yarn add react-godfather`\n\n### Usage\n\n`import { toC/*, Wait*/ } from 'react-godfather`\n\n### Understanding\n\n#### `toC`\n\nTo use `react-godfather`, you wrap your component with `toC`:\n\n```javascript\nconst Foo = toC(() =\u003e {\n  return () =\u003e (\u003cdiv\u003eHi!\u003c/div\u003e)\n})\n```\n\n`toC` is a function with the following parameters:\n`(f, events = ['onClick'], opts)`\n\n| name | description |\n| f | your component |\n| events | the list events you want it to automatically react upon - defaults to `onClick`|\n| opts | a configuration object : `{ id :: String, stopPropagation :: Bool, extraClass :: String }` |\n\n#### What props does your component receive?\n\nStraight from the source's ... mouth: \n\n```javascript\n  const componentProps = {\n    ...props,\n    prevProps,\n    __dbg: dbg,\n    tick,\n    withTick: x =\u003e () =\u003e x().then(tick),\n    onUnmount: onUnmountReceiver\n  }\n```\n\n* `props` are exactly what you expect - the props passed to your component by your own code\n* `tick` is the function you call to explicitly trigger an update.\n* `withTick` is a wrapper function to reduce the usefulness of `tick` :)\n* `onUnmount` lets you provide a function to be run in the context of your component on unmount.\n\n#### `Wait`\n\n`Wait` is a simple, straightforward helper component:\n\n```javascript\n  return (\n    \u003cWait\n      until={() =\u003e data}\n      launch={() =\u003e getMyData().then(props.tick)}\n      lounge={(\u003cdiv\u003eLoading......\u003c/div\u003e)}\n    \u003e\n      \u003cdiv\u003e\u003cbutton onClick={handleClick}\u003e{data}\u003c/button\u003e\u003c/div\u003e\n    \u003c/Wait\u003e\n  )\n```\n\n## FAQ\n\n### But why?\n\nFirst let me state clearly that Hooks are technically awesome. Godfather is itself a function component with Hooks. \nAnd while conceptualizing and building `react-godfather`, I came to understand and appreciate some design decisions\nthat the React team had to make - facing similar questions made me realize some of the clever answers they came up with.\n\nReact-godfather came out as my answer to not-so-technical but human concerns.\n\nHooks are great once you've really \"gotten\" them. It's a different way of reasoning than the \"normal\" way\nof writing JavaScript. Therefore, it raises the bar for junior colleagues. You've probably seen the struggle if\nyou've been involved in teams that have a mix of seniors and juniors. Some will get it faster than others and - in some\ncases - some won't truly get it at all (don't forget that not every colleague happens to have a CS background).\n\nOne could argue that we shouldn't disregard sophistication for practicality. And mostly, I agree. But are you using\nPureScript in production? Because if we're talking about really valuing sophistication in the JavaScript ecosystem, \nis there any excuse not to go 100% in \ninstead of just [pretending really hard](https://www.youtube.com/watch?v=IvPBMEYxP-Y)?\n\nIt is clear that we already make a huge concessions, because reality imposes constraints to our ideal development \npractices. In that sense, I do think that it is worth making it easier for new entrants to write modern (classes are out) \nReact code without (excuse the pun) `useHairPulling`.\n\nAnother reason is stylistic and more of a preference. I - for one - simply enjoy better the top-down style of \nreasoning about code. \nMaybe because I started with QBasic :) So this does scratch that itch.\n\n### What about speed? Isn't checking for deep equality slow?\n\n`react-godfather`'s comparison is based off a custom fork of [dequal](https://github.com/lukeed/dequal). \n`dequal` boasts ~ 1.7 million ops per second for Object comparisons,\nwhich I guess is enough for almost every app out there that isn't aiming for 60 fps. \nPlus, remember that `react-godfather` plays well with everything, so you can just skip using it for that pesky \ncomponent that really has to squeeze out all those nanoseconds of performance.\n\n### How do I integrate with the Context API?\n\n*(Thanks to Leonso Medina for bringing up the question!)*\n\nYou can use the `.Consumer` context component as usual. See [this example](https://codesandbox.io/s/react-godfather-docs-demo-context-4k7dz) in the sandbox.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkapolos%2Freact-godfather","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkapolos%2Freact-godfather","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkapolos%2Freact-godfather/lists"}