├── packages ├── .gitkeep ├── playground │ ├── src │ │ ├── app │ │ │ ├── app.scss │ │ │ └── app.tsx │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── styles.scss │ │ ├── polyfills.ts │ │ ├── main.tsx │ │ └── index.html │ ├── .babelrc │ ├── .eslintrc.json │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── .browserslistrc └── react-rxjs │ ├── .babelrc │ ├── src │ ├── index.ts │ ├── use-until-destroyed │ │ ├── use-until-destroyed.ts │ │ └── use-until-destroyed.spec.ts │ ├── use-effect │ │ ├── use-effect.ts │ │ └── use-effect.spec.tsx │ ├── use-from-event │ │ ├── use-from-event.spec.tsx │ │ └── use-from-event.ts │ └── use-observable │ │ ├── use-observable.ts │ │ └── use-observable.spec.tsx │ ├── tsconfig.lib.json │ ├── jest.config.js │ ├── .eslintrc.json │ ├── tsconfig.spec.json │ ├── tsconfig.json │ └── package.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── .browserslistrc ├── .prettierrc ├── babel.config.json ├── logo.png ├── .prettierignore ├── jest.preset.js ├── .husky └── commit-msg ├── jest.config.js ├── .vscode └── extensions.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── 3-support-request.md │ ├── 2-feature-request.yaml │ └── 1-bug-report.yaml ├── workflows │ └── ci.yml └── PULL_REQUEST_TEMPLATE.md ├── tsconfig.base.json ├── .gitignore ├── nx.json ├── .eslintrc.json ├── package.json ├── migrations.json ├── README.md └── workspace.json /packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 1 Chrome version -------------------------------------------------------------------------------- /packages/playground/src/app/app.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/playground/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngneat/react-rxjs/HEAD/logo.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /packages/playground/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /packages/playground/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngneat/react-rxjs/HEAD/packages/playground/src/favicon.ico -------------------------------------------------------------------------------- /packages/playground/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | module.exports = { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner", 6 | "dbaeumer.vscode-eslint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/playground/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-rxjs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic", 7 | "useBuiltIns": "usage" 8 | } 9 | ] 10 | ], 11 | "plugins": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /packages/playground/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import App from './app/app'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useObservable } from './use-observable/use-observable'; 2 | export { useUntilDestroyed } from './use-until-destroyed/use-until-destroyed'; 3 | export { useEffect$ } from './use-effect/use-effect'; 4 | export { useFromEvent } from './use-from-event/use-from-event'; -------------------------------------------------------------------------------- /packages/react-rxjs/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc" 5 | }, 6 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx"], 7 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/react-rxjs/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'react-rxjs', 3 | preset: '../../jest.preset.js', 4 | transform: { 5 | '^.+\\.[tj]sx?$': 'babel-jest', 6 | }, 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 8 | coverageDirectory: '../../coverage/packages/react-rxjs', 9 | }; 10 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Support Request' 3 | about: Questions and requests for support 4 | --- 5 | 6 | Please do not file questions or support requests on the GitHub issues tracker. 7 | 8 | You can get your questions answered using other communication channels. Please see: 9 | 10 | https://github.com/ngneat/react-rxjs/discussions 11 | 12 | Thank you! 13 | -------------------------------------------------------------------------------- /packages/playground/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Playground 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/playground/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-rxjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 9 | "../../node_modules/@nrwl/react/typings/image.d.ts" 10 | ], 11 | "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], 12 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-rxjs/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.app.json" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-rxjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.lib.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/playground/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by: 2 | # 1. autoprefixer to adjust CSS to support the below specified browsers 3 | # 2. babel preset-env to adjust included polyfills 4 | # 5 | # For additional information regarding the format and rule options, please see: 6 | # https://github.com/browserslist/browserslist#queries 7 | # 8 | # If you need to support different browsers in production, you may tweak the list below. 9 | 10 | last 1 Chrome version 11 | last 1 Firefox version 12 | last 2 Edge major versions 13 | last 2 Safari major version 14 | last 2 iOS major versions 15 | Firefox ESR 16 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": false, 11 | "target": "ES2017", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "strict": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@ngneat/react-rxjs": ["packages/react-rxjs/src/index.ts"] 20 | } 21 | }, 22 | "exclude": ["node_modules", "tmp"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-until-destroyed/use-until-destroyed.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useEffect } from 'react'; 2 | import { Subject, MonoTypeOperatorFunction } from 'rxjs'; 3 | import { takeUntil } from 'rxjs/operators'; 4 | 5 | export function useUntilDestroyed() { 6 | const subject = useMemo(() => new Subject(), []); 7 | 8 | const data = useMemo(() => ({ 9 | untilDestroyed(): MonoTypeOperatorFunction { 10 | return takeUntil(subject.asObservable()); 11 | }, 12 | destroyed: subject.asObservable() 13 | }), [subject]); 14 | 15 | useEffect(() => { 16 | return () => subject.next(true); 17 | }, [subject]); 18 | 19 | return data; 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-effect/use-effect.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef, useEffect, DependencyList } from 'react'; 2 | import { Observable, Subscription } from 'rxjs'; 3 | 4 | export function useEffect$(sourceFactory$: () => Observable, deps: DependencyList = []) { 5 | // eslint-disable-next-line react-hooks/exhaustive-deps 6 | const $ = useMemo(() => sourceFactory$(), []); 7 | const sub = useRef(); 8 | 9 | useEffect(() => { 10 | sub.current = $.subscribe(); 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 13 | return () => sub.current!.unsubscribe(); 14 | // eslint-disable-next-line react-hooks/exhaustive-deps 15 | }, [$, ...deps]); 16 | 17 | return sub.current; 18 | } -------------------------------------------------------------------------------- /packages/react-rxjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngneat/react-rxjs", 3 | "version": "1.1.0", 4 | "description": "React goodies for applications that uses RxJS", 5 | "repository": { 6 | "url": "https://github.com/ngneat/react-rxjs" 7 | }, 8 | "readme": "https://github.com/ngneat/react-rxjs/blob/master/README.md", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "keywords": [ 13 | "rxjs", 14 | "react", 15 | "react hooks", 16 | "observable hook", 17 | "until destroyed hook", 18 | "effects", 19 | "react rxjs" 20 | ], 21 | "author": { 22 | "name": "Netanel Basal", 23 | "url": "https://netbasal.com" 24 | }, 25 | "license": "MIT", 26 | "peerDependencies": { 27 | "rxjs": "*", 28 | "react": "*" 29 | } 30 | } -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-until-destroyed/use-until-destroyed.spec.ts: -------------------------------------------------------------------------------- 1 | import { useUntilDestroyed } from './use-until-destroyed'; 2 | import { renderHook } from '@testing-library/react-hooks'; 3 | import { interval } from 'rxjs'; 4 | import { finalize } from 'rxjs/operators'; 5 | import { useEffect } from 'react'; 6 | 7 | jest.useFakeTimers(); 8 | 9 | const spy = jest.fn(); 10 | 11 | function useTest() { 12 | 13 | const { untilDestroyed } = useUntilDestroyed(); 14 | 15 | useEffect(() => { 16 | interval(1000).pipe(untilDestroyed(), finalize(spy)).subscribe(); 17 | }, [untilDestroyed]) 18 | } 19 | 20 | describe('useObservable', () => { 21 | it('should destroy', () => { 22 | const result = renderHook(() => useTest()); 23 | result.unmount(); 24 | expect(spy).toHaveBeenCalledTimes(1); 25 | }); 26 | }); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request' 2 | description: Suggest a feature for React RxJS Library 3 | 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Description 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | id: proposed-solution 14 | attributes: 15 | label: Proposed solution 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | id: alternatives-considered 21 | attributes: 22 | label: Alternatives considered 23 | validations: 24 | required: true 25 | 26 | - type: dropdown 27 | id: contribute 28 | attributes: 29 | label: Do you want to create a pull request? 30 | options: 31 | - 'Yes' 32 | - 'No' 33 | validations: 34 | required: true -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@nrwl/workspace/presets/npm.json", 3 | "npmScope": "ngneat-react", 4 | "affected": { 5 | "defaultBase": "master" 6 | }, 7 | "tasksRunnerOptions": { 8 | "default": { 9 | "runner": "@nrwl/workspace/tasks-runners/default", 10 | "options": { 11 | "cacheableOperations": ["build", "lint", "test", "e2e"], 12 | "parallel": 1 13 | } 14 | } 15 | }, 16 | "cli": { 17 | "defaultCollection": "@nrwl/react" 18 | }, 19 | "generators": { 20 | "@nrwl/react": { 21 | "application": { 22 | "style": "scss", 23 | "linter": "eslint", 24 | "babel": true 25 | }, 26 | "component": { 27 | "style": "scss" 28 | }, 29 | "library": { 30 | "style": "scss", 31 | "linter": "eslint" 32 | } 33 | } 34 | }, 35 | "defaultProject": "playground" 36 | } 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-effect/use-effect.spec.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Observable, timer } from 'rxjs'; 3 | import { finalize, tap } from 'rxjs/operators'; 4 | import { render } from '@testing-library/react'; 5 | import { useEffect$ } from './use-effect'; 6 | 7 | jest.useFakeTimers(); 8 | 9 | describe('useEffect$', () => { 10 | const spy = jest.fn(); 11 | const destroySpy = jest.fn(); 12 | 13 | const source$ = timer(1000).pipe( 14 | tap(spy), 15 | finalize(destroySpy) 16 | ) 17 | 18 | function FooComponent() { 19 | useEffect$(() => source$); 20 | 21 | return
22 | } 23 | 24 | 25 | it('should register/unregister effects', () => { 26 | const { unmount } = render(); 27 | 28 | jest.advanceTimersByTime(1000); 29 | 30 | expect(spy).toHaveBeenCalledTimes(1); 31 | 32 | unmount(); 33 | 34 | expect(destroySpy).toHaveBeenCalledTimes(1); 35 | 36 | jest.useRealTimers(); 37 | }) 38 | }) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: '@ngneat/react-rxjs' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: true 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Cache node modules 17 | uses: actions/cache@v2 18 | env: 19 | cache-name: cache-node-modules 20 | with: 21 | path: ~/.npm 22 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 23 | restore-keys: | 24 | ${{ runner.os }}-build-${{ env.cache-name }}- 25 | ${{ runner.os }}-build- 26 | ${{ runner.os }}- 27 | - uses: actions/setup-node@v2 28 | with: 29 | node-version: '14' 30 | cache: 'npm' 31 | 32 | - name: Install dependencies 33 | run: npm i 34 | 35 | - name: Run ESLint 36 | run: npm run lint 37 | 38 | - name: Run unit tests 39 | run: npm run test 40 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-from-event/use-from-event.spec.tsx: -------------------------------------------------------------------------------- 1 | import { finalize, tap } from 'rxjs/operators'; 2 | import { render, fireEvent } from '@testing-library/react'; 3 | import { ChangeEvent } from 'react'; 4 | import { useFromEvent } from './use-from-event'; 5 | 6 | describe('useFromEvent', () => { 7 | const spy = jest.fn(); 8 | const destroySpy = jest.fn(); 9 | 10 | function SearchComponent() { 11 | const { ref } = useFromEvent>('change', (event$) => 12 | event$.pipe( 13 | tap(spy), 14 | finalize(destroySpy) 15 | ) 16 | ); 17 | 18 | return 19 | } 20 | 21 | 22 | it('should register the event', () => { 23 | const { getByTestId, unmount } = render(); 24 | 25 | fireEvent.change(getByTestId('input')); 26 | 27 | expect(spy).toHaveBeenCalledTimes(1); 28 | 29 | fireEvent.change(getByTestId('input')); 30 | 31 | expect(spy).toHaveBeenCalledTimes(2); 32 | 33 | unmount(); 34 | 35 | expect(destroySpy).toHaveBeenCalledTimes(1); 36 | 37 | }) 38 | }) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug in the React RxJS Library 3 | 4 | body: 5 | - type: dropdown 6 | id: is-regression 7 | attributes: 8 | label: Is this a regression? 9 | options: 10 | - 'Yes' 11 | - 'No' 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: Description 19 | validations: 20 | required: true 21 | 22 | - type: input 23 | id: reproduction 24 | attributes: 25 | label: Please provide a link to a minimal reproduction of the bug 26 | 27 | - type: textarea 28 | id: exception-or-error 29 | attributes: 30 | label: Please provide the exception or error you saw 31 | render: true 32 | 33 | - type: textarea 34 | id: environment 35 | attributes: 36 | label: Please provide the environment you discovered this bug in 37 | render: true 38 | 39 | - type: textarea 40 | id: other 41 | attributes: 42 | label: Anything else? 43 | 44 | - type: dropdown 45 | id: contribute 46 | attributes: 47 | label: Do you want to create a pull request? 48 | options: 49 | - 'Yes' 50 | - 'No' 51 | validations: 52 | required: true 53 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | 3 | Please check if your PR fulfills the following requirements: 4 | 5 | - [ ] The commit message follows our guidelines: https://github.com/ngneat/react-rxjs/blob/master/CONTRIBUTING.md#commit 6 | - [ ] Tests for the changes have been added (for bug fixes / features) 7 | - [ ] Docs have been added / updated (for bug fixes / features) 8 | 9 | ## PR Type 10 | 11 | What kind of change does this PR introduce? 12 | 13 | 14 | 15 | ``` 16 | [ ] Bugfix 17 | [ ] Feature 18 | [ ] Code style update (formatting, local variables) 19 | [ ] Refactoring (no functional changes, no api changes) 20 | [ ] Build related changes 21 | [ ] CI related changes 22 | [ ] Documentation content changes 23 | [ ] Other... Please describe: 24 | ``` 25 | 26 | ## What is the current behavior? 27 | 28 | 29 | 30 | Issue Number: N/A 31 | 32 | ## What is the new behavior? 33 | 34 | ## Does this PR introduce a breaking change? 35 | 36 | ``` 37 | [ ] Yes 38 | [ ] No 39 | ``` 40 | 41 | 42 | 43 | ## Other information 44 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-from-event/use-from-event.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, useRef, useEffect, SyntheticEvent } from 'react'; 2 | import { Observable, Subscription, Subject, fromEvent } from 'rxjs'; 3 | 4 | export function useFromEvent ? Element : unknown>( 5 | eventName: string, 6 | action$: (event$: Observable) => Observable, 7 | { deps }: { deps: DependencyList } = { deps: [] } 8 | ) { 9 | const action = useRef(action$); 10 | const eleRef = useRef(null); 11 | 12 | useEffect(() => { 13 | let subscription: Subscription | null = new Subscription(); 14 | let subject: Subject | null = null; 15 | 16 | if (eleRef.current) { 17 | subject = new Subject(); 18 | subscription.add(action.current(subject.asObservable()).subscribe()); 19 | 20 | subscription.add(fromEvent(eleRef.current as unknown as Element, eventName).subscribe(v => { 21 | subject?.next(v as unknown as Event) 22 | })); 23 | } 24 | 25 | return () => { 26 | subscription?.unsubscribe(); 27 | subscription = null; 28 | subject = null; 29 | }; 30 | // eslint-disable-next-line react-hooks/exhaustive-deps 31 | }, [eleRef.current, ...deps, eventName]); 32 | 33 | return { ref: eleRef } 34 | } -------------------------------------------------------------------------------- /packages/playground/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect$, useFromEvent, useObservable } from '@ngneat/react-rxjs'; 2 | import { interval } from 'rxjs'; 3 | import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; 4 | import { fromFetch } from 'rxjs/fetch'; 5 | import { ChangeEvent, useState } from 'react'; 6 | 7 | function loadTodos() { 8 | return fromFetch<{ id: number }[]>('https://jsonplaceholder.typicode.com/todos', { 9 | selector: (response) => response.json(), 10 | }).pipe( 11 | tap({ 12 | next(todos) { 13 | console.log(todos); 14 | }, 15 | }) 16 | ); 17 | } 18 | 19 | 20 | const counter$ = interval(2000); 21 | 22 | export function App() { 23 | const [show, setShow] = useState(true); 24 | const [sideEffect, setSideEffect] = useState(1) 25 | const [count] = useObservable(counter$, { initialValue: 0 }); 26 | const [text, setText] = useState(''); 27 | useEffect$(() => loadTodos(), [sideEffect]); 28 | 29 | const { ref } = useFromEvent>('keyup', (event$) => 30 | event$.pipe( 31 | debounceTime(400), 32 | distinctUntilChanged(), 33 | tap((event) => { 34 | console.log(event); 35 | setText(event.target.value) 36 | }) 37 | ) 38 | ); 39 | 40 | return ( 41 |
42 | {count} 43 | 44 | 45 | 46 |

useFromEvent

47 |

{text}

48 | 49 | {show && } 50 |
51 | ) 52 | } 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-observable/use-observable.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { Observable, Subscription } from 'rxjs'; 3 | import { useEffect, useRef, useState, DependencyList, useMemo, useReducer } from 'react'; 4 | 5 | export function useObservable( 6 | source$: Observable, 7 | { deps = [], initialValue }: { deps?: DependencyList, initialValue?: T } = {} 8 | ): [T, { error: E | undefined, completed: boolean, subscription: Subscription | undefined }] { 9 | 10 | const sourceRef = useMemo(() => source$, deps); 11 | const subscription = useRef(new Subscription()); 12 | const nextValue = useRef(initialValue); 13 | const [error, setError] = useState(); 14 | const [completed, setCompleted] = useState(false); 15 | const emitsInitialSyncValue = initialValue === undefined; 16 | const [_, forceUpdate] = useReducer(x => x + 1, 0); 17 | 18 | useMemo(() => { 19 | if (emitsInitialSyncValue) { 20 | let subscription: Subscription | null = sourceRef.subscribe(v => { 21 | nextValue.current = v; 22 | }); 23 | 24 | subscription.unsubscribe(); 25 | subscription = null; 26 | } 27 | }, deps); 28 | 29 | useEffect(() => { 30 | let firstEmission = true; 31 | 32 | subscription.current = sourceRef.subscribe({ 33 | next(value) { 34 | if (emitsInitialSyncValue && firstEmission) { 35 | firstEmission = false; 36 | } else { 37 | nextValue.current = value; 38 | forceUpdate(); 39 | } 40 | }, 41 | error: setError, 42 | complete: setCompleted.bind(null, true) 43 | }) 44 | 45 | return () => { 46 | subscription?.current.unsubscribe(); 47 | } 48 | }, deps); 49 | 50 | 51 | return [nextValue.current!, { error, completed, subscription: subscription.current }]; 52 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngneat-react", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve playground", 7 | "test": "nx run-many --target=test --exclude=playground --all", 8 | "lint": "nx run-many --target=lint --exclude=playground --all", 9 | "build": "nx build react-rxjs", 10 | "c": "git-cz", 11 | "prepare": "husky install", 12 | "update": "nx migrate latest", 13 | "migrate": "nx migrate --run-migrations" 14 | }, 15 | "private": true, 16 | "devDependencies": { 17 | "@commitlint/cli": "^13.1.0", 18 | "@commitlint/config-conventional": "^13.1.0", 19 | "@jscutlery/semver": "^2.8.0", 20 | "@nrwl/cli": "13.2.2", 21 | "@nrwl/eslint-plugin-nx": "13.2.2", 22 | "@nrwl/jest": "13.2.2", 23 | "@nrwl/linter": "13.2.2", 24 | "@nrwl/react": "13.2.2", 25 | "@nrwl/tao": "13.2.2", 26 | "@nrwl/web": "13.2.2", 27 | "@nrwl/workspace": "13.2.2", 28 | "@testing-library/react": "12.1.2", 29 | "@testing-library/react-hooks": "7.0.2", 30 | "@types/jest": "27.0.2", 31 | "@types/node": "14.14.33", 32 | "@types/react": "17.0.30", 33 | "@types/react-dom": "17.0.9", 34 | "@typescript-eslint/eslint-plugin": "4.33.0", 35 | "@typescript-eslint/parser": "4.33.0", 36 | "babel-jest": "27.2.3", 37 | "commitizen": "^4.2.4", 38 | "cz-conventional-changelog": "^3.3.0", 39 | "eslint": "7.32.0", 40 | "eslint-config-prettier": "8.1.0", 41 | "eslint-plugin-import": "2.25.2", 42 | "eslint-plugin-jsx-a11y": "6.4.1", 43 | "eslint-plugin-react": "7.26.1", 44 | "eslint-plugin-react-hooks": "4.2.0", 45 | "expect-type": "^0.12.0", 46 | "husky": "^7.0.2", 47 | "jest": "27.2.3", 48 | "prettier": "^2.3.1", 49 | "ts-jest": "27.0.5", 50 | "typescript": "~4.3.5" 51 | }, 52 | "dependencies": { 53 | "core-js": "^3.6.5", 54 | "git-cz": "^4.7.1", 55 | "react": "17.0.2", 56 | "react-dom": "17.0.2", 57 | "regenerator-runtime": "0.13.7", 58 | "rxjs": "^6.6.7", 59 | "tslib": "^2.0.0" 60 | }, 61 | "config": { 62 | "commitizen": { 63 | "path": "git-cz" 64 | } 65 | }, 66 | "commitlint": { 67 | "extends": [ 68 | "@commitlint/config-conventional" 69 | ] 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "version": "13.0.0-beta.1", 5 | "description": "Add default base to nx.json if its not currently set", 6 | "factory": "./src/migrations/update-13-0-0/set-default-base-if-not-set", 7 | "cli": "nx", 8 | "package": "@nrwl/workspace", 9 | "name": "set-default-base-if-not-set" 10 | }, 11 | { 12 | "version": "13.0.0-beta.4", 13 | "description": "Move global settings into nx.json, and project specific settings into workspace.json", 14 | "cli": "nx", 15 | "implementation": "./src/migrations/update-13-0-0/config-locations/config-locations", 16 | "package": "@nrwl/workspace", 17 | "name": "13-0-0-config-locations" 18 | }, 19 | { 20 | "version": "13.2.0", 21 | "description": "Set --parallel=1 for existing repos to preserve the existing behavior", 22 | "cli": "nx", 23 | "implementation": "./src/migrations/update-13-2-0/set-parallel-default", 24 | "package": "@nrwl/workspace", 25 | "name": "set-parallel-default" 26 | }, 27 | { 28 | "version": "13.1.2-beta.0", 29 | "cli": "nx", 30 | "description": "Support .test. file names in tsconfigs", 31 | "factory": "./src/migrations/update-13-1-2/update-tsconfigs-for-tests", 32 | "package": "@nrwl/jest", 33 | "name": "update-ts-config-for-test-filenames" 34 | }, 35 | { 36 | "cli": "nx", 37 | "version": "13.0.0-beta.0", 38 | "description": "Update tsconfig.json to use `jsxImportSource` to support css prop", 39 | "factory": "./src/migrations/update-13-0-0/update-emotion-setup", 40 | "package": "@nrwl/react", 41 | "name": "update-emotion-setup-13.0.0" 42 | }, 43 | { 44 | "cli": "nx", 45 | "version": "13.0.0-beta.0", 46 | "description": "Migrate Storybook to use webpack 5", 47 | "factory": "./src/migrations/update-13-0-0/migrate-storybook-to-webpack-5", 48 | "package": "@nrwl/react", 49 | "name": "migrate-storybook-to-webpack-5-13.0.0" 50 | }, 51 | { 52 | "cli": "nx", 53 | "version": "13.0.0-beta.1", 54 | "description": "Removes deprecated node-sass package (sass is already a dependency of @nrwl/web).", 55 | "factory": "./src/migrations/update-13-0-0/remove-node-sass-13-0-0", 56 | "package": "@nrwl/web", 57 | "name": "remove-node-sass-13-0-0" 58 | }, 59 | { 60 | "cli": "nx", 61 | "version": "13.0.0-beta.1", 62 | "description": "Remove packages installed by Nx 12's `@nrwl/web:webpack5` generator.", 63 | "factory": "./src/migrations/update-13-0-0/remove-webpack-5-packages-13-0-0", 64 | "package": "@nrwl/web", 65 | "name": "remove-webpack-5-packages" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-rxjs/src/use-observable/use-observable.spec.tsx: -------------------------------------------------------------------------------- 1 | import { useObservable } from './use-observable'; 2 | import { renderHook, act } from '@testing-library/react-hooks'; 3 | import { interval, BehaviorSubject, of } from 'rxjs'; 4 | import { finalize, map } from 'rxjs/operators'; 5 | import { useState } from 'react'; 6 | import { render, fireEvent } from '@testing-library/react'; 7 | 8 | jest.useFakeTimers(); 9 | 10 | const store = new BehaviorSubject('1'); 11 | 12 | function getStream$(id: string) { 13 | return of(id); 14 | } 15 | 16 | const SomeComponent = ({ id }: { id: string }) => { 17 | const [state] = useObservable(getStream$(id), { deps: [id] }) 18 | 19 | return

{state}

20 | } 21 | 22 | const OuterComponent = () => { 23 | const [id, setId] = useState('1'); 24 | 25 | return <> 26 | 27 | 28 | 29 | } 30 | 31 | 32 | describe('Deps change', () => { 33 | it('should subscribe to the new obseravble', () => { 34 | const { getByTestId } = render(); 35 | expect(getByTestId('p').innerHTML).toBe('1'); 36 | fireEvent.click(getByTestId('btn')); 37 | expect(getByTestId('p').innerHTML).toBe('2'); 38 | }) 39 | }) 40 | 41 | describe('useObservable', () => { 42 | 43 | beforeEach(() => jest.clearAllTimers()); 44 | 45 | it('should update every second', () => { 46 | const { result } = renderHook(() => useObservable(interval(1000), { initialValue: -1 })); 47 | let [next] = result.current; 48 | 49 | expect(next).toBe(-1); 50 | 51 | act(() => { 52 | jest.advanceTimersByTime(1000); 53 | }); 54 | 55 | [next] = result.current; 56 | 57 | expect(next).toBe(0); 58 | 59 | act(() => { 60 | jest.advanceTimersByTime(1000); 61 | }); 62 | 63 | [next] = result.current; 64 | 65 | expect(next).toBe(1); 66 | }); 67 | 68 | 69 | 70 | it('should return an error', () => { 71 | const { result } = renderHook(() => useObservable(of(1).pipe(map(error => { 72 | throw new Error('error'); 73 | })))); 74 | 75 | const [next, { error, completed }] = result.current; 76 | 77 | expect(error).toEqual(new Error('error')); 78 | expect(next).toEqual(undefined); 79 | expect(completed).toEqual(false) 80 | }) 81 | 82 | 83 | it('should support BehaviorSubject', () => { 84 | const query = new BehaviorSubject('init'); 85 | const { result } = renderHook(() => useObservable(query)); 86 | let [next] = result.current; 87 | 88 | expect(next).toBe('init'); 89 | 90 | act(() => query.next('2')); 91 | 92 | [next] = result.current; 93 | expect(next).toBe('2'); 94 | }); 95 | it('should unsubscribe', () => { 96 | const spy = jest.fn(); 97 | 98 | const { result, unmount } = renderHook(() => useObservable(interval(1000).pipe(finalize(spy)), { initialValue: -1 })); 99 | 100 | // // eslint-disable-next-line prefer-const 101 | let [next] = result.current; 102 | 103 | expect(next).toBe(-1); 104 | 105 | act(() => { 106 | jest.advanceTimersByTime(1000); 107 | }); 108 | 109 | [next] = result.current; 110 | 111 | expect(next).toBe(0); 112 | 113 | unmount(); 114 | expect(spy).toHaveBeenCalled(); 115 | }) 116 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | > "Plug and play" for RxJS Observables in React Apps! 6 | 7 | [![@ngneat/react-rxjs](https://github.com/ngneat/react-rxjs/actions/workflows/ci.yml/badge.svg)](https://github.com/ngneat/react-rxjs/actions/workflows/ci.yml) 8 | ![commitizen](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square) 9 | ![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square) 10 | ![coc-badge](https://img.shields.io/badge/codeof-conduct-ff69b4.svg?style=flat-square) 11 | ![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e5079.svg?style=flat-square]https://github.com/semantic-release/semantic-release) 12 | ![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square]https://github.com/prettier/prettier) 13 | 14 | 15 | ```bash 16 | npm install @ngneat/react-rxjs 17 | ``` 18 | 19 | ## useObservable 20 | 21 | Ever had an Observable holding data that you need to maintain in the state of your React App? This hook bridges that gap. 22 | 23 | It receives an Observable, subscribes to it, and stores the current version in a react state, ensuring that it persists between re-renders. 24 | 25 | Note that you can use it multiple times, with various Observables. 26 | 27 | ```tsx 28 | import { interval } from 'rxjs'; 29 | import { take } from 'rxjs/operators'; 30 | import { useObservable } from '@ngneat/react-rxjs'; 31 | 32 | const interval$ = interval(1000); 33 | 34 | function CounterComponent() { 35 | const [counter] = useObservable(interval$); 36 | const [counter, { error, completed, subscription }] = useObservable(interval$.pipe(take(3))); 37 | 38 | return

{counter}

; 39 | } 40 | ``` 41 | 42 | `useObservable` can take the initial value as the second parameter - `useObservable(source$, initialValue)`. If the source fires synchronously immediately (like in a `BehaviorSubject`), the value will be used as the initial value. 43 | 44 | You can also pass a dependencies: 45 | 46 | ```tsx 47 | import { useObservable } from '@ngneat/react-rxjs'; 48 | 49 | const SomeComponent = ({ id }: { id: string }) => { 50 | const [state] = useObservable(getStream$(id), { deps: [id] }) 51 | 52 | return state; 53 | } 54 | ``` 55 | 56 | 57 | ## useUntilDestroyed 58 | 59 | The `useUntilDestroyed` hook returns an object with two properties: 60 | 61 | - `untilDestroyed`: An operator that unsubscribes from the `source` when the component is destroyed. 62 | 63 | - `destroyed` - An observable that emits when the component is destroyed. 64 | 65 | 66 | ```ts 67 | import { interval } from 'rxjs'; 68 | import { useUntilDestroyed } from '@ngneat/react-rxjs'; 69 | 70 | function CounterComponent() { 71 | const { untilDestroyed } = useUntilDestroyed(); 72 | 73 | useEffect(() => { 74 | interval(1000).pipe(untilDestroyed()).subscribe(console.log) 75 | }, []) 76 | 77 | return ...; 78 | } 79 | ``` 80 | 81 | ## useEffect$ 82 | The `useEffect$` hook receives a function that returns an observable, subscribes to it, and unsubscribes when the component destroyed: 83 | 84 | ```ts 85 | import { useEffect$ } from '@ngneat/react-rxjs'; 86 | 87 | function loadTodos() { 88 | return fromFetch('todos').pipe(tap({ 89 | next(todos) { 90 | updateStore(todos); 91 | } 92 | })); 93 | } 94 | 95 | function TodosComponent() { 96 | const [todos] = useObservable(todos$); 97 | 98 | useEffect$(() => loadTodos()); 99 | useEffect$(() => loadTodos(), deps); 100 | 101 | return <>{todos}; 102 | } 103 | ``` 104 | ## useFromEvent 105 | It's the `fromEvent` observable, but with hooks: 106 | 107 | ```ts 108 | export function App() { 109 | const [text, setText] = useState(''); 110 | 111 | const { ref } = useFromEvent>('keyup', (event$) => 112 | event$.pipe( 113 | debounceTime(400), 114 | distinctUntilChanged(), 115 | tap((event) => setText(event.target.value)) 116 | ) 117 | ); 118 | 119 | return ( 120 | <> 121 | 122 | { text } 123 | 124 | ) 125 | ``` 126 | 127 | 128 | 129 |
Icons made by Freepik from www.flaticon.com
-------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "playground": { 5 | "root": "packages/playground", 6 | "sourceRoot": "packages/playground/src", 7 | "projectType": "application", 8 | "targets": { 9 | "build": { 10 | "executor": "@nrwl/web:build", 11 | "outputs": ["{options.outputPath}"], 12 | "options": { 13 | "outputPath": "dist/packages/playground", 14 | "index": "packages/playground/src/index.html", 15 | "main": "packages/playground/src/main.tsx", 16 | "polyfills": "packages/playground/src/polyfills.ts", 17 | "tsConfig": "packages/playground/tsconfig.app.json", 18 | "assets": [ 19 | "packages/playground/src/favicon.ico", 20 | "packages/playground/src/assets" 21 | ], 22 | "styles": ["packages/playground/src/styles.scss"], 23 | "scripts": [], 24 | "webpackConfig": "@nrwl/react/plugins/webpack" 25 | }, 26 | "configurations": { 27 | "production": { 28 | "fileReplacements": [ 29 | { 30 | "replace": "packages/playground/src/environments/environment.ts", 31 | "with": "packages/playground/src/environments/environment.prod.ts" 32 | } 33 | ], 34 | "optimization": true, 35 | "outputHashing": "all", 36 | "sourceMap": false, 37 | "extractCss": true, 38 | "namedChunks": false, 39 | "extractLicenses": true, 40 | "vendorChunk": false, 41 | "budgets": [ 42 | { 43 | "type": "initial", 44 | "maximumWarning": "500kb", 45 | "maximumError": "1mb" 46 | } 47 | ] 48 | } 49 | } 50 | }, 51 | "serve": { 52 | "executor": "@nrwl/web:dev-server", 53 | "options": { 54 | "buildTarget": "playground:build", 55 | "hmr": true, 56 | "port": 4201 57 | }, 58 | "configurations": { 59 | "production": { 60 | "buildTarget": "playground:build:production", 61 | "hmr": false 62 | } 63 | } 64 | }, 65 | "lint": { 66 | "executor": "@nrwl/linter:eslint", 67 | "outputs": ["{options.outputFile}"], 68 | "options": { 69 | "lintFilePatterns": ["packages/playground/**/*.{ts,tsx,js,jsx}"] 70 | } 71 | }, 72 | "version": { 73 | "executor": "@jscutlery/semver:version" 74 | } 75 | }, 76 | "tags": [] 77 | }, 78 | "react-rxjs": { 79 | "root": "packages/react-rxjs", 80 | "sourceRoot": "packages/react-rxjs/src", 81 | "projectType": "library", 82 | "targets": { 83 | "build": { 84 | "executor": "@nrwl/web:package", 85 | "outputs": ["{options.outputPath}"], 86 | "options": { 87 | "outputPath": "dist/packages/react-rxjs", 88 | "tsConfig": "packages/react-rxjs/tsconfig.lib.json", 89 | "project": "packages/react-rxjs/package.json", 90 | "entryFile": "packages/react-rxjs/src/index.ts", 91 | "external": ["react/jsx-runtime"], 92 | "rollupConfig": "@nrwl/react/plugins/bundle-rollup", 93 | "assets": [ 94 | { 95 | "glob": "README.md", 96 | "input": ".", 97 | "output": "." 98 | } 99 | ] 100 | } 101 | }, 102 | "lint": { 103 | "executor": "@nrwl/linter:eslint", 104 | "outputs": ["{options.outputFile}"], 105 | "options": { 106 | "lintFilePatterns": ["packages/react-rxjs/**/*.{ts,tsx,js,jsx}"] 107 | } 108 | }, 109 | "test": { 110 | "executor": "@nrwl/jest:jest", 111 | "outputs": ["coverage/packages/react-rxjs"], 112 | "options": { 113 | "jestConfig": "packages/react-rxjs/jest.config.js", 114 | "passWithNoTests": true 115 | } 116 | }, 117 | "version": { 118 | "executor": "@jscutlery/semver:version" 119 | } 120 | }, 121 | "tags": [] 122 | } 123 | } 124 | } 125 | --------------------------------------------------------------------------------