{"id":15696394,"url":"https://github.com/neurosnap/react-cofx","last_synced_at":"2025-05-08T23:22:08.189Z","repository":{"id":138553259,"uuid":"138118628","full_name":"neurosnap/react-cofx","owner":"neurosnap","description":"Fetch data for a react component with a declarative side-effects library","archived":false,"fork":false,"pushed_at":"2018-09-15T13:43:23.000Z","size":447,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T19:21:14.852Z","etag":null,"topics":["cosed","fetch","fetcher","react","redux-saga","saga","side-effects"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neurosnap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-21T04:20:37.000Z","updated_at":"2023-12-25T11:39:40.000Z","dependencies_parsed_at":"2023-06-07T21:45:34.344Z","dependency_job_id":null,"html_url":"https://github.com/neurosnap/react-cofx","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Freact-cofx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Freact-cofx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Freact-cofx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Freact-cofx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neurosnap","download_url":"https://codeload.github.com/neurosnap/react-cofx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253161528,"owners_count":21863754,"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":["cosed","fetch","fetcher","react","redux-saga","saga","side-effects"],"created_at":"2024-10-03T19:08:54.645Z","updated_at":"2025-05-08T23:22:08.159Z","avatar_url":"https://github.com/neurosnap.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-cofx [![Build Status](https://travis-ci.org/neurosnap/react-cofx.svg?branch=master)](https://travis-ci.org/neurosnap/react-cofx)\n\nMake async calls when rendering a component.\n\n## Motivation\n\nSome components need to request data but don't require being stored in redux.\nFor example, if the data is only being used for that one component, it is not\nnecessary to place it into redux. However, leveraging lifecycle methods like\n`componentDidMount` and `setState` has its own set of problems because of the way react handles\nmounting. For example, an unmounted component cannot call `setState` so if an\nasync request comes back when the component gets unmounted then that will result in\nan error. People get around that issue by adding a class property `this._mounted`\nand then setting it to `false` when unmounting. This adds quite a bit of complexity\nto a component and some argue is an [anti-pattern](https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html).\nThere are also cases where the component will not unmount when you expect it to\nand the async function will not be activated.\n\nThis library attempts to create a simple API for calling an async\nfunction and returning some data to the component. This separates data fetching\nfrom the actual component and uses roughly the same API that `react-redux` and\n`redux-saga` uses to keep things easy to test and migrate to a redux container component.\n\n## Features\n\n- Separates data fetching from component\n- Connects a component to an async function\n- Caches response of async function\n- Async function has access to component's props\n- Component has ability to refetch data\n- Same structure as connecting a component to `redux` for easy migration\n- Async function can be anything that [co](https://github.com/tj/co) can convert to a promise\n- Leverages [cofx](https://github.com/neurosnap/cofx) which handles side effects as data\n- Testing is simple and familiar if previously used `redux` and `redux-saga`\n- Upgradability to [redux-cofx](https://github.com/neurosnap/redux-cofx) and [redux-saga](https://github.com/redux-saga/redux-saga)\n\n## Upgrade plan\n\nSometimes all we need to do is fetch data from a server and load it into one\ncomponent. That data lives inside that component and we don't need to share\nit with anything else. This is where `react-cofx` shines. However, because\nrequirements change over time, we need to eventually share that data with\nmultiple components and now we need to save the data in redux. We could\nuse `redux-thunk` to accomplish that goal, but it's difficult to test thunks\nand the way to fetch the data looks a little different than `react-cofx`.\nThis is where `redux-cofx` shines. The function we wrote for `react-cofx`\nlooks almost identical to the one we use for `redux-cofx`. The only addition\nis `redux-cofx` provides the ability to query redux state and dispatch actions.\nSometimes requirements change even more and now we need the ability to listen to multiple events and other\nmore complex flow control mechanisms. This is when we would introduce `redux-saga`.\nThe `cofx` API was built with `redux-saga` in mind. It's the same exact API.\nAll we would need to do is change the import path for `call`, etc. to `redux-saga/effects`\nand it should work exactly the same.\n\nSo what do we have? We have an upgrade path to start small (react-cofx), upgrade\nto redux with simple side-effects (redux-cofx), and then upgrade to really complex\nflow mechanisms (redux-saga).\n\n`react-cofx` -\u003e `redux-cofx` -\u003e `redux-saga`\n\n## Usage\n\n```js\nyarn add react-cofx\n```\n\n```js\nimport createFetcher from \"react-cofx\";\n\nconst fetch = window.fetch;\n\nasync function fetchMovies() {\n  const resp = await fetch(\"http://httpbin.org/get?movies=one,two,three\");\n  const json = await resp.json();\n  const movies = json.args.movies.split(\",\");\n\n  return movies;\n}\n\nconst DisplayMovies = ({ data = [] }) =\u003e (\n  \u003cdiv\u003e\n    {data.map(movie =\u003e (\n      \u003cdiv key={movie}\u003e{movie}\u003c/div\u003e\n    ))}\n  \u003c/div\u003e\n);\n\nconst movieFetcher = createFetcher(fetchMovies);\nconst DisplayMoviesContainer = movieFetcher()(DisplayMovies);\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cDisplayMoviesContainer /\u003e\n  \u003c/div\u003e\n);\n```\n\nThe async function could be anything that [co](https://github.com/tj/co) supports.\nA generator, a promise, an array of promises, an object of promises, even a normal function.\n`react-cofx` will take the result of what you pass it and send it to the component.\n\nHere at ReactFetcher, Inc. we like to use [cofx](https://github.com/neurosnap/cofx)\nwhich treats side effects as data with an API similar to `redux-saga`:\n\n```js\nimport createFetcher from \"react-cofx\";\nimport { call } from \"cofx\";\n\nconst fetch = window.fetch;\n\nfunction* fetchMovies() {\n  const resp = yield call(fetch, \"http://httpbin.org/get?movies=one,two,three\");\n  const json = yield call([resp, \"json\"]);\n  const movies = json.args.movies.split(\",\");\n\n  return movies;\n}\n```\n\nUsing cofx makes testing side effects simple.\n\nWant to change the way the data gets sent to the component? Use `mapStateToProps`\n\n```js\nimport createFetcher from \"react-cofx\";\n\nconst fetch = window.fetch;\n\nasync function fetchMovies() {\n  const resp = await fetch(\"http://httpbin.org/get?movies=one,two,three\");\n  const json = await resp.json();\n  const movies = json.args.movies.split(\",\");\n\n  return movies;\n}\n\nconst DisplayMovies = ({ data = [] }) =\u003e (\n  \u003cdiv\u003e\n    {movies.map(movie =\u003e (\n      \u003cdiv key={movie}\u003e{movie}\u003c/div\u003e\n    ))}\n  \u003c/div\u003e\n);\n\nconst movieFetcher = createFetcher(fetchMovies);\nconst mapStateToProps = movies =\u003e ({ movies }); // default mapStateToProps: (data, error) =\u003e ({ data, error });\nconst DisplayMoviesContainer = movieFetcher(mapStateToProps)(DisplayMovies);\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cDisplayMoviesContainer /\u003e\n  \u003c/div\u003e\n);\n```\n\nWant to refetch data? Like `mapDispatchToProps` in redux, we have `mapRefetchToProps`\nwhich will bust the cache and call the request again.\n\n```js\nconst DisplayMovies = ({ movies = [], refetch }) =\u003e {\n  return h(\"div\", [\n    h(\n      \"a\",\n      {\n        href: \"#\",\n        onClick: () =\u003e {\n          refetch();\n        }\n      },\n      \"refetch\"\n    ),\n    h(\"div\", movies.map(movie =\u003e h(\"div\", { key: movie }, movie)))\n  ]);\n};\n\nconst movieFetcher = createFetcher(fetchMovies);\nconst mapStateToProps = movies =\u003e ({ movies });\nconst mapRefetchToProps = refetch =\u003e ({ refetch });\nconst DisplayMoviesContainer = movieFetcher(mapStateToProps, mapRefetchToProps)(\n  DisplayMovies\n);\n```\n\nAsync function also receives props sent to component\n\n```js\nfunction* fetchMovies({ movieName }) {\n  const resp = yield call(fetch, `http://httpbin.org/get?movies=${movieName}`);\n  const json = yield call([resp, \"json\"]);\n  const movies = json.args.movies.split(\",\");\n\n  return movies;\n}\n\nconst DisplayMovies = ({ movies = [] }) =\u003e (\n  \u003cdiv\u003e\n    {movies.map(movie =\u003e (\n      \u003cdiv key={movie}\u003e{movie}\u003c/div\u003e\n    ))}\n  \u003c/div\u003e\n);\n\nconst movieFetcher = createFetcher(fetchMovies);\nconst mapStateToProps = movies =\u003e ({ movies });\nconst DisplayMoviesContainer = movieFetcher(mapStateToProps)(DisplayMovies);\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cDisplayMoviesContainer movieName=\"Transporter\" /\u003e\n  \u003c/div\u003e\n);\n```\n\nAsync function returns an error? `mapStateToProps` has a second parameter for\nany error states that are returned from the async function\n\n```js\nfunction* fetchMovies({ movieName }) {\n  throw new Error(\"Something bad happened\");\n}\n\nconst DisplayMovies = ({ movies = [], error }) =\u003e {\n  if (error) {\n    return \u003cdiv\u003e{error.message}\u003c/div\u003e;\n  }\n\n  return (\n    \u003cdiv\u003e\n      {movies.map(movie =\u003e (\n        \u003cdiv key={movie}\u003e{movie}\u003c/div\u003e\n      ))}\n    \u003c/div\u003e\n  );\n};\n\nconst movieFetcher = createFetcher(fetchMovies);\nconst mapStateToProps = (movies, error) =\u003e ({\n  movies: movies || [],\n  error\n});\nconst DisplayMoviesContainer = movieFetcher(mapStateToProps)(DisplayMovies);\n\nconst App = () =\u003e (\n  \u003cdiv\u003e\n    \u003cDisplayMoviesContainer movieName=\"Transporter\" /\u003e\n  \u003c/div\u003e\n);\n```\n\nWant a loader?\n\n```js\nconst Loader = () =\u003e \u003cdiv\u003eLOADING!\u003c/div\u003e;\nconst DisplayMoviesContainer = movieFetcher(mapStateToProps)(\n  DisplayMovies,\n  Loader\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosnap%2Freact-cofx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneurosnap%2Freact-cofx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosnap%2Freact-cofx/lists"}