├── 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 |
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 | setId('2')}>Change id
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 | [](https://github.com/ngneat/react-rxjs/actions/workflows/ci.yml)
8 | 
9 | 
10 | 
11 | 
12 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------