{"id":28089439,"url":"https://github.com/bigab/use-epic","last_synced_at":"2025-09-02T06:35:26.764Z","repository":{"id":55452122,"uuid":"188964809","full_name":"BigAB/use-epic","owner":"BigAB","description":"Use RxJS Epics as state management for your React Components","archived":false,"fork":false,"pushed_at":"2025-08-21T16:08:41.000Z","size":2863,"stargazers_count":47,"open_issues_count":7,"forks_count":4,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-28T13:35:47.802Z","etag":null,"topics":["epic","hook","observable","react","rxjs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/BigAB.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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,"zenodo":null}},"created_at":"2019-05-28T06:09:04.000Z","updated_at":"2025-05-21T05:56:59.000Z","dependencies_parsed_at":"2025-07-06T18:50:03.247Z","dependency_job_id":null,"html_url":"https://github.com/BigAB/use-epic","commit_stats":null,"previous_names":["bigab/use-state-epic"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/BigAB/use-epic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigAB%2Fuse-epic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigAB%2Fuse-epic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigAB%2Fuse-epic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigAB%2Fuse-epic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BigAB","download_url":"https://codeload.github.com/BigAB/use-epic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigAB%2Fuse-epic/sbom","scorecard":{"id":21769,"data":{"date":"2025-08-11","repo":{"name":"github.com/BigAB/use-epic","commit":"fe2146054bd14bf95855dc9514ce5f755443a9f9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"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":"Code-Review","score":0,"reason":"Found 0/6 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":"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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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 28 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"}},{"name":"Vulnerabilities","score":0,"reason":"69 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-3j8f-xvm3-ffx4","Warn: Project is vulnerable to: GHSA-4p35-cfcx-8653","Warn: Project is vulnerable to: GHSA-7f3x-x4pr-wqhj","Warn: Project is vulnerable to: GHSA-jpp7-7chh-cf67","Warn: Project is vulnerable to: GHSA-q6wq-5p59-983w","Warn: Project is vulnerable to: GHSA-j9fq-vwqv-2fm2","Warn: Project is vulnerable to: GHSA-pqw5-jmp5-px4v","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-4rq4-32rv-6wp6","Warn: Project is vulnerable to: GHSA-64g7-mvw6-v9qj","Warn: Project is vulnerable to: GHSA-28v4-jf82-jvj8","Warn: Project is vulnerable to: GHSA-7f3x-2wcx-hww8","Warn: Project is vulnerable to: GHSA-8f8g-9j73-7p82","Warn: Project is vulnerable to: GHSA-93q5-3xpc-8vg3","Warn: Project is vulnerable to: GHSA-gvjw-8mmr-8f6g","Warn: Project is vulnerable to: GHSA-rgqx-226f-2xp4","Warn: Project is vulnerable to: GHSA-vwhq-pm3r-fjm9","Warn: Project is vulnerable to: GHSA-wc4x-qmr2-rj8h","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-14T16:55:54.875Z","repository_id":55452122,"created_at":"2025-08-14T16:55:54.875Z","updated_at":"2025-08-14T16:55:54.875Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273244304,"owners_count":25070958,"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-09-02T02:00:09.530Z","response_time":77,"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":["epic","hook","observable","react","rxjs"],"created_at":"2025-05-13T12:58:03.368Z","updated_at":"2025-09-02T06:35:26.277Z","avatar_url":"https://github.com/BigAB.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🏰 use-epic\n\nUse RxJS Epics as state management for your React Components\n\n[![Build Status](https://travis-ci.org/bigab/use-epic.svg?branch=master)](https://travis-ci.org/bigab/use-epic)\n[![MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/bigab/use-epic/blob/master/LICENSE)\n[![npm version](https://badge.fury.io/js/use-epic.svg)](https://badge.fury.io/js/use-epic) [![Greenkeeper badge](https://badges.greenkeeper.io/BigAB/use-epic.svg)](https://greenkeeper.io/)\n\n### What is an **Epic**❓\n\nAn **Epic** is a function which takes an Observable of actions (`action$`), an Observable of the current state (`state$`), and an object of dependencies (`deps`) and returns an Observable.\n\nThe idea of the **Epic** comes out of the fantastic redux middleware [redux-observable](https://redux-observable.js.org/), but a _noteable difference_ is that, because redux-observable is redux middleware, the observable returned from the **Epic** emits new `actions` to be run through **reducers** to create new state, `useEpic()` skips the redux middleman and expects the\n**Epic** to return an observable of `state` updates.\n\n```js\nfunction Epic(action$, state$, deps) {\n  return newState$;\n}\n```\n\nThis simple idea opens up all the fantastic abilites of [RxJS](https://rxjs.dev/) to your React components with a simple but powerful API.\n\n## :mag_right: Usage\n\n```js\nfunction productEpic(action$, state$, deps) {\n  const { productStore, cartObserver, props$ } = deps;\n  combineLatest(action$.pipe(ofType('addToCart')), state$)\n    .pipe(\n      map(([productId, products]) =\u003e products.find(p =\u003e p.id === productId))\n    )\n    .subscribe(cartObserver);\n\n  return props$.pipe(\n    map(props =\u003e props.category),\n    switchMap(category =\u003e productStore.productsByCategory(category)),\n    startWith([])\n  );\n}\n\nconst ProductsComponent = props =\u003e {\n  const [products, dispatch] = useEpic(productEpic, { props });\n\n  // map dispatch to a component callback\n  const addToCart = productId =\u003e\n    dispatch({ type: 'addToCart', payload: productId });\n\n  return \u003cProductsList products={products} addToCart={addToCart} /\u003e;\n};\n```\n\n## :hammer_and_pick: Installation\n\n`use-epic` requires both `react` and `rxjs` as **peer dependencies**.\n\n```sh\nnpm install use-epic rxjs react\n```\n\n```sh\nyarn add use-epic rxjs react\n```\n\n## :card_file_box: Examples\n\nSee [examples](./examples) locally with `npm run examples`\n\n**[![Simple Fetch Example - CodeSandbox](https://img.shields.io/badge/example-simple_fetch-black?logo=codesandbox\u0026style=for-the-badge)](https://codesandbox.io/s/use-epic-simple-ajax-list-load-wtl2r?fontsize=14\u0026module=%2Fsrc%2FPeopleList.js)**\n**([source examples](./examples/simple-fetch/PeopleList.js))**\n\n**[![Alarm Clock Example - CodeSandbox](https://img.shields.io/badge/example-alarm_clock-black?logo=codesandbox\u0026style=for-the-badge)](https://codesandbox.io/s/alarm-clock-5x9vy?fontsize=14)**\n**([source examples](./examples/alarm-clock/main.js))**\n\n[Beer Search] _\\*coming soon\\*_  \n[Pull to Refresh] _\\*coming soon\\*_  \n[Working with simple-store] _\\*coming soon\\*_  \n\n## :book: API\n\n- [`useEpic()`](#useepic)\n- [`epic()`](#epic)\n- [`ofType()`](#oftype)\n- [`\u003cEpicDepsProvider\u003e`](#epicdepsprovider)\n\n### `useEpic()`\n\nA [React hook](https://reactjs.org/docs/hooks-intro.html) for using RxJS Observables for state management.\n\n#### `const [state, dispatch] = useEpic( epic, options? );`\n\nThe `useEpic()` hook, accepts an `epic` function, and an `options` object, and returns a tuple of `state` and a `dispatch` callback, similar to [`useReducer()`](https://reactjs.org/docs/hooks-reference.html#usereducer).\n\n**arguments**\n\n- `epic` an [epic](#epic) function, [described below](#epic) .\n\n  `function myEpic( action$, state$, deps ) { return newState$ }`\n\n  It should be noted, that only the first Epic function passed to `useEpic()` will be retained, so if you write your function inline like:\n\n  ```js\n  const [state] = useEpic((action$, state$, deps) =\u003e {\n    return action$.pipe(switchMap(action =\u003e fetchData(action.id)));\n  });\n  ```\n\n  ...any variable closures used in the epic will not change, and component renders will generate a new `Epic` function that will merely be discared. For that reason we encourage defining Epics outside of the component.\n\n- `options` \u003csup\u003e_\\*optional_\u003c/sup\u003e an object with some special properties:\n  - `deps` - an object with keys, any key/values on this deps object will be available on the `deps` argument in the `Epic` function\n  - `props` - a way to \"pass\" component props into the `Epic`, anything passed here will be emitted to the special, always available, `deps.props$`, in the `Epic`. This should be used with caution, as it limits portability, but is available for when dispatching an action is not appropriate.\n\n```js\nconst CatDetails = props =\u003e {\n  const [cat] = useEpic(kittenEpic, { deps: { kittenService }, props: cat.id });\n  \u003cDetails subject={cat} /\u003e;\n};\n```\n\n### `epic()`\n\nAn **`epic`** is a function, that accepts an Observable of actions (`action$`), an Observable of the current state (`state$`), and an object of dependencies (`deps`) and returns an Observable of `stateUpdates$`.\n\n#### `function myEpic( action$, state$, deps ) { return newState$ }`\n\nThe **`epic`** will be called by `useEpic()`, passing the `action$`, `state$` and `deps` arguments, and it may either return a new [RxJS Observable](https://rxjs.dev/api/index/class/Observable) or `undefined`. If an observable is returned, and values emitted from that observable are set as `state`, the first element of the tuple returned from `useEpic()`.\n\n```js\nconst [state, dispatch] = useEpic(epic);\n```\n\n**arguments passed when the epic is called**\n\n- `action$` An observable of dispatched `actions`. The `actions` emitted are anything passed to the `dispatch()` callback returned from `useEpic()`. They can be anything, but by convention are often either objects with a `type`, `payload` and sometimes `meta` properties (e.g. `{ type: 'activate', payload: user }`), or an array tuple with the `type` as the first element and the payload as the second (e.g. `['activate', user]`).\n\n- `state$` An observable of the current `state`. It can be sometimes helpful to have a reference to the current state when composing streams, say if your `action.payload` is an `id` and you'd like to map that to a state entity before further processing it. Unless the observable returned from `useEpic()` has initial state, from using `startWith()` or a `BehaviorSubject`, this will emit `undefined` to start.  \n   ⚠️ Caution: When using `state$` it is possible to find yourself in an inifinte asynchronous loop. Take care in how it is used along with the returned `newState$` observable.\n\n- `deps` an object of key/value pairs provided by the `options` of `useEpic` when it is called, or from the `\u003cEpicDepsProvider\u003e` component.\n\n  The `deps` argument can be very useful for provding a dependency injection point into your `Epic`s and therefore into your components. For example, if you provide an `ajax` dependecy in deps, you could provide the RxJS `ajax` function by default, but stub out `ajax` for tests or demo pages by wrapping your component in an `\u003cEpicDepsProvider\u003e` component.\n\n  ```js\n    const kittyEpic = (action$, state$, { ajax: rxjs.ajax }) =\u003e {\n      return action$.pipe(\n        switchMap(({ payload: id })=\u003e ajax(`/api/kittens/${id}`))\n      );\n    }\n\n    const KittyComponent = () =\u003e {\n      const [kitty, dispatch] = useEpic(kittyEpic);\n\n      //... render and such\n    }\n\n    // mocking for tests\n    test('should load kitty details when clicked', async () =\u003e {\n      // stub out ajax for the test\n      const fakeResponse = { name: 'Snuggles', breed: 'tabby' };\n      const ajaxStub = jest.fn(() =\u003e Promise.resolve(fakeResponse));\n\n      const { getByLabelText, getByText } = render(\n        \u003cEpicDepsProvider ajax={ajaxStub}\u003e\n          \u003cKittyComponent /\u003e\n        \u003c/EpicDepsProvider\u003e\n      );\n\n      fireEvent.click(getByLabelText(/Cat #1/i));\n      const detailsName = await getByText(/^Name:/);\n      expect(detailsName.textContent).toBe('Name: Snuggles')\n    });\n  ```\n\n  The `deps` object can be good for providing \"services\", config, or any number of other useful features to help decouple your components from their dependecies.\n\n  `deps.props$`  \n  There is a special property `props$` which is always provided by `useEpic()` and is the methods in which components can pass props into the Epic. The `options.props` property of the `useEpic()` call is always emitted to the `deps.props$` observable.\n\n### `ofType()`\n\nA [RxJS Operator](https://rxjs.dev/guide/operators) for convient filtering of action\\$ by `type`\n\n#### `action$.pipe( ofType( type, ...moreTypes? ) );`\n\nJust a convinience operator for filtering `actions` by type, from either the action itself `'promote'`, the conventional object form `{ type: 'promote', payload: { id: 23 } }` or array form `['promote', { id: 23 }]`. The `ofType()` operator only filters, so your `type` property will still be in the emitted value for the next operator or subscription.\n\n**arguments**\n\n- `type` the `ofType()` operator can take one or more `type` arguments to match on, if any of the `types` match for the action emitted, the `action` will be emitted further down the stream. The `type` arguments are not restriced to `Strings`, they can be anything including symbols, functions or objects. They are matched with SameValueZero (pretty much `===`) comparison.\n\n```js\nconst promotionChange$ = action$.pipe(ofType('promote', 'depromote'));\n```\n\n### `\u003cEpicDepsProvider\u003e`\n\nA React Provider component that supplies `deps` to any `epic` function used by the `useEpic()` hook, called anywhere lower in the component tree, just like Reacts [`context.Provider`](https://reactjs.org/docs/context.html#contextprovider)\n\n```jsx\n\u003cEpicDepsProvider kittenService={kittenService} catConfig={config}\u003e\n  \u003cApp /\u003e\n\u003c/EpicDepsProvider\u003e\n\n// const kittyEpic = ( action$, state$, { kittenService, catConfig }) =\u003e {\n//  ...\n// }\n```\n\nAny `props` passed to the `EpicDepsProvider` component will be merged onto the `deps` object passed to the `epic` function when calling `useEpic()`. Any change in `deps` will unsubscribe from the `newState$` observable, and recall the `epic` function, setting up new subscriptions, so try to change `deps` sparingly.\n\n## Testing\n\nOne benefit of using Epics for state management is that they are easy to test. Because they are just functions, you can ensure the behaviour of your Epic, just by calling it with some test observables and deps, emitting actions, and asserting on the `newState$` emitted.\n\n_TODO: Create testing example_  \n_TODO: Create epic testing helper method_\n\n## :seedling: Contribute\n\nThink you'd like to contribute to this project? Check out our [contributing guideline](./CONTRIBUTING.md) and feel free to create issues and pull requests!\n\n## License\n\nMIT © [Adam L Barrett](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigab%2Fuse-epic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbigab%2Fuse-epic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigab%2Fuse-epic/lists"}