Using npm:
$ npm i --save rxjs-hooks
Or yarn:
$ yarn add rxjs-hooks
import React from "react";
import ReactDOM from "react-dom";
import { useObservable } from "rxjs-hooks";
import { interval } from "rxjs";
import { map } from "rxjs/operators";
function App() {
const value = useObservable(() => interval(500).pipe(map((val) => val * 3)));
return (
<div className="App">
<h1>Incremental number: {value}</h1>
</div>
);
}
import React from "react";
import ReactDOM from "react-dom";
import { useEventCallback } from "rxjs-hooks";
import { map } from "rxjs/operators";
function App() {
const [clickCallback, [description, x, y]] = useEventCallback((event$) =>
event$.pipe(
map((event) => [event.target.innerHTML, event.clientX, event.clientY]),
),
["nothing", 0, 0],
)
return (
<div className="App">
<h1>click position: {x}, {y}</h1>
<h1>"{description}" was clicked.</h1>
<button onClick={clickCallback}>click me</button>
<button onClick={clickCallback}>click you</button>
<button onClick={clickCallback}>click him</button>
</div>
);
}
type RestrictArray<T> = T extends any[] ? T : []
type InputFactory<State, Inputs = undefined> = Inputs extends undefined
? (state$: Observable<State>) => Observable<State>
: (inputs$: Observable<RestrictArray<Inputs>>, state$: Observable<State>) => Observable<State>
declare function useObservable<State>(inputFactory: InputFactory<State>): State | null
declare function useObservable<State>(inputFactory: InputFactory<State>, initialState: State): State
declare function useObservable<State, Inputs>(
inputFactory: InputFactory<State, Inputs>,
initialState: State,
inputs: RestrictArray<Inputs>,
): State
import React from 'react'
import ReactDOM from 'react-dom'
import { useObservable } from 'rxjs-hooks'
import { of } from 'rxjs'
function App() {
const value = useObservable(() => of(1000))
return (
// render twice
// null and 1000
<h1>{value}</h1>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
With default value:
import React from 'react'
import ReactDOM from 'react-dom'
import { useObservable } from 'rxjs-hooks'
import { of } from 'rxjs'
function App() {
const value = useObservable(() => of(1000), 200)
return (
// render twice
// 200 and 1000
<h1>{value}</h1>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
Observe props change:
import React from 'react'
import ReactDOM from 'react-dom'
import { useObservable } from 'rxjs-hooks'
import { of } from 'rxjs'
import { map } from 'rxjs/operators'
function App(props: { foo: number }) {
const value = useObservable((inputs$) => inputs$.pipe(
map(([val]) => val + 1),
), 200, [props.foo])
return (
// render three times
// 200 and 10001 and 2001
<h1>{value}</h1>
)
}
ReactDOM.render(<App foo={1000} />, document.querySelector('#app'))
ReactDOM.render(<App foo={2000} />, document.querySelector('#app'))
useObservable with state$
import React from 'react'
import ReactDOM from 'react-dom'
import { useObservable } from 'rxjs-hooks'
import { interval } from 'rxjs'
import { map, withLatestFrom } from 'rxjs/operators'
function App() {
const value = useObservable((state$) => interval(1000).pipe(
withLatestFrom(state$),
map(([_num, state]) => state * state),
), 2)
return (
// 2
// 4
// 16
// 256
// ...
<h1>{value}</h1>
)
}
ReactDOM.render(<App />, document.querySelector('#root'))
type RestrictArray<T> = T extends any[] ? T : []
type VoidAsNull<T> = T extends void ? null : T
type EventCallbackState<EventValue, State, Inputs = void> = [
(val: EventValue) => void,
[State extends void ? null : State, BehaviorSubject<State | null>, BehaviorSubject<RestrictArray<Inputs> | null>]
]
type ReturnedState<EventValue, State, Inputs> = [
EventCallbackState<EventValue, State, Inputs>[0],
EventCallbackState<EventValue, State, Inputs>[1][0]
]
type EventCallback<EventValue, State, Inputs> = Inputs extends void
? (eventSource$: Observable<EventValue>, state$: Observable<State>) => Observable<State>
: (
eventSource$: Observable<EventValue>,
inputs$: Observable<RestrictArray<Inputs>>,
state$: Observable<State>,
) => Observable<State>
declare function useEventCallback<EventValue, State = void>(
callback: EventCallback<EventValue, State, void>,
): ReturnedState<EventValue, State | null, void>
declare function useEventCallback<EventValue, State = void>(
callback: EventCallback<EventValue, State, void>,
initialState: State,
): ReturnedState<EventValue, State, void>
declare function useEventCallback<EventValue, State = void, Inputs = void>(
callback: EventCallback<EventValue, State, Inputs>,
initialState: State,
inputs: RestrictArray<Inputs>,
): ReturnedState<EventValue, State, Inputs>
import React from 'react'
import ReactDOM from 'react-dom'
import { useEventCallback } from 'rxjs-hooks'
import { mapTo } from 'rxjs/operators'
function App() {
const [clickCallback, value] = useEventCallback((event$: Observable<React.SyntheticEvent<HTMLButtonElement>>) =>
event$.pipe(
mapTo(1000)
)
)
return (
// render null
// click button
// render 1000
<>
<h1>{value}</h1>
<button onClick={clickCallback}>click me</button>
</>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
With initial value:
import React from 'react'
import ReactDOM from 'react-dom'
import { useEventCallback } from 'rxjs-hooks'
import { mapTo } from 'rxjs/operators'
function App() {
const [clickCallback, value] = useEventCallback((event$: Observable<React.SyntheticEvent<HTMLButtonElement>>) =>
event$.pipe(
mapTo(1000)
),
200,
)
return (
// render 200
// click button
// render 1000
<>
<h1>{value}</h1>
<button onClick={clickCallback}>click me</button>
</>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
With state$:
import React from "react";
import ReactDOM from "react-dom";
import { useEventCallback } from "rxjs-hooks";
import { map, withLatestFrom } from "rxjs/operators";
function App() {
const [clickCallback, [description, x, y, prevDescription]] = useEventCallback(
(event$, state$) =>
event$.pipe(
withLatestFrom(state$),
map(([event, state]) => [
event.target.innerHTML,
event.clientX,
event.clientY,
state[0],
])
),
["nothing", 0, 0, "nothing"]
);
return (
<div className="App">
<h1>
click position: {x}, {y}
</h1>
<h1>"{description}" was clicked.</h1>
<h1>"{prevDescription}" was clicked previously.</h1>
<button onClick={clickCallback}>click me</button>
<button onClick={clickCallback}>click you</button>
<button onClick={clickCallback}>click him</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
A complex example: useEventCallback with both inputs$ and state$
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { useEventCallback } from "rxjs-hooks";
import { map, withLatestFrom, combineLatest } from "rxjs/operators";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const [clickCallback, [description, x, y, prevDesc]] = useEventCallback(
(event$, inputs$, state$) =>
event$.pipe(
map(event => [event.target.innerHTML, event.clientX, event.clientY]),
combineLatest(inputs$),
withLatestFrom(state$),
map(([eventAndInput, state]) => {
const [[text, x, y], [count]] = eventAndInput;
const prevDescription = state[0];
return [text, x + count, y + count, prevDescription];
})
),
["nothing", 0, 0, "nothing"],
[count]
);
return (
<div className="App">
<h1>
click position: {x}, {y}
</h1>
<h1>"{description}" was clicked.</h1>
<h1>"{prevDesc}" was clicked previously.</h1>
<button onClick={clickCallback}>click me</button>
<button onClick={clickCallback}>click you</button>
<button onClick={clickCallback}>click him</button>
<div>
<p>
click buttons above, and then click this `+++` button, the position
numbers will grow.
</p>
<button onClick={() => setCount(count + 1)}>+++</button>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);