React-Rxjs hooks v0.7.0: React hooks for RxJS

icon
Latest Release: v0.7.0
  • make rxjs and react as peer deps
Source code(tar.gz)
Source code(zip)

React hooks for RxJS

CircleCI codecov npm version

Installation

Using npm:

$ npm i --save rxjs-hooks

Or yarn:

$ yarn add rxjs-hooks

Quick look

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>
  );
}

Apis

useObservable

export type InputFactory<State> = (state$: Observable<State>) => Observable<State>
export type InputFactoryWithInputs<State, Inputs> = (
  state$: Observable<State>,
  inputs$: Observable<RestrictArray<Inputs>>,
) => Observable<State>

export function useObservable<State>(inputFactory: InputFactory<State>): State | null
export function useObservable<State>(inputFactory: InputFactory<State>, initialState: State): State
export function useObservable<State, Inputs>(
  inputFactory: InputFactoryWithInputs<State, Inputs>,
  initialState: State,
  inputs: RestrictArray<Inputs>,
): State

Examples:

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 1001 and 2001
    <h1>{value}</h1>
  )
}

ReactDOM.render(<App foo={1000} />, document.querySelector('#app'))
ReactDOM.render(<App foo={2000} />, document.querySelector('#app'))

useObservable with state$

live demo

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'))

useEventCallback

Examples:

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$:

live demo

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$

live demo

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$, state$, inputs$) =>
      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);

Example of combining callback observables coming from separate elements - animation with start/stop button and rate controllable via slider

live demo

const Animation = ({ frame }) => {
  const frames = "|/-\\|/-\\|".split("");
  return (
    <div>
      <p>{frames[frame % frames.length]}</p>
    </div>
  );
};


const App = () => {
  const defaultRate = 5;

  const [running, setRunning] = useState(false);

  const [onEvent, frame] = useEventCallback(events$ => {
    const running$ = events$.pipe(
      filter(e => e.type === "click"),
      scan(running => !running, running),
      startWith(running),
      tap(setRunning)
    );

    return events$.pipe(
      filter(e => e.type === "change"),
      map(e => parseInt(e.target.value, 10)),
      startWith(defaultRate),
      switchMap(i => timer(200, 1000 / i)),
      withLatestFrom(running$),
      filter(([_, running]) => running),
      scan(frame => frame + 1, 0)
    );
  });

  return (
    <div className="App">
      <button onClick={onEvent}>{running ? "Stop" : "Start"}</button>
      <input
        type="range"
        onChange={onEvent}
        defaultValue={defaultRate}
        min="1"
        max="10"
      ></input>
      <Animation frame={frame} />
    </div>
  );
};

Comments

  • Error and loading state guide
    Error and loading state guide

    Nov 16, 2018

    Expose API like:

    const value = useObservable((props$: Observable<[number, string]>) => props$.pipe(
      switchMap(([n, s]) => httpRequest.query(`https://mockbackend?n=${n}&s=${s}`)),
    ))
    
    documents 
    Reply
  • Request: pass in observable directly instead of factory
    Request: pass in observable directly instead of factory

    Nov 21, 2018

    Great library. I would be great if I could just pass an observable into the hook directly:

    useObservable(myObservable);

    instead of

    useObservable(() => myObservable);

    question 
    Reply
  • Typing error when wrapping useObservable with generic
    Typing error when wrapping useObservable with generic

    Apr 23, 2019

    import { useObservable } from "rxjs-hooks";
    import { map  } from "rxjs/operators";
    
    export const useAsync = <State, Inputs extends any[]>(
      fn: (...args: Inputs) => State,
      deps: Inputs,
    ) =>
      useObservable<State, Inputs>(
        inputs$ =>
          inputs$.pipe(
            map(i => {
              return (i as unknown) as State;
            }),
          ),
        [0, 1, 2],
        deps,
      );
    
    

    Results in the following: image

    It seems that passing generic values for useObservable isn't supported. Any ideas?

    Reply
  • [performance] create state$ and input$ base on callback length
    [performance] create state$ and input$ base on callback length

    Jul 8, 2019

    Currently Subject state$ and input$ are always created. How about checking the length of the callback argument and only create the Subjects that are used by the callback.

    One caveat of this approach is the length check does not recognize spread arguments and the use of arguments object. But I think it is rare to use callback this way.

    Reply
  • Question about VoidableEventCallback
    Question about VoidableEventCallback

    Jul 16, 2019

    I was trying write something like

    const [callback, flag] = useEventCallback<
      React.MouseEvent<HTMLElement> | boolean,
      boolean
    >($events => $events.pipe(mapTo(false)), false)
    
    callback(true)
    

    And there was type error.

    Argument of type 'true' is not assignable to parameter of type 'false & true & MouseEvent<HTMLElement, MouseEvent>'.
      Type 'true' is not assignable to type 'false'.ts(2345)
    

    Checking the source

    https://github.com/LeetCode-OpenSource/rxjs-hooks/blob/2b781f12d0c54a079cca9e419153e50e0388fcb0/src/use-event-callback.ts#L6

    It seems like for example

    type Callback = VoidableEventCallback<string | boolean>
    

    will be resolved as

    type Callback = ((e: string) => void) | ((e: false) => void) | ((e: true) => void)
    

    which means e has to be string & boolean. Is this a bug or am I missing something here?

    Reply
  • RFC: decouple `useEventCallback` from the state management means
    RFC: decouple `useEventCallback` from the state management means

    Aug 10, 2019

    Hi, thank you for the inspiration and make this awesome libs. I have the following RFC proposed:

    Suggestion

    Remove the overlapping of state management in useEventCallback, as we have useObservable, useState, and useReducer already, making this hook just purely for the async event management. This hook is best to be treated as an adaptor to convert DOMEvent to DOMEvent stream.

    Why

    After using it for a while, I always think useEventCallback is not so intuitive to use. It accepts up to 3 arguments: callback, initialState, and inputs. Why we need this initialState if we want to introduce prop dependencies? At the current implementation, what this hook would returns is not just a memorized event handler as a callback, but a tuple of handler + the observed state, which makes me feel this hook is doing too much, IMHO.

    Ideally, what I have in mind is to have a similar interface as in the the native useCallback. But what can make this hook so useful is to convert the event object to an observable. Then we can manage DOMEvent as a stream easily.

    If we need to performance side effects, like to update state, we should use tap operator instead. For example:

    const deps = /* some dependencies, could be state, or props */
    const [state, setState] = useState();
    
    const onClick = useEventCallback(
      (event$, deps$) => event$.pipe(
        filter((e) => e.type === 'click'),
        map(({ target }) => target.value),
        tap((v) => setState(v))    // * re-render explicitly requested by the user.
        withLatestFrom(deps$),
        /* do something with dependencies */
      ),
      [deps] // optional
    )
    

    The deps would be any props or local state dependencies that is reactively emitted and passed as the second props to the callback. And we can think this like an epic in redux-observable. This way we decouple the state management part out of this hook. And this hook is never going to trigger re-render by itself, it will have to be introduced by the user.

    Implementation

    Here is the suggested implementation:

    const createEvent$ = <T>() => {
      return new Subject<T>();
    };
    
    const createDeps$ = <D extends unknown[]>(deps: D) => {
      return deps.length ? new BehaviorSubject(deps) : undefined;
    };
    
    export const useEventCallback = <E = any, D extends unknown[] = never>(
      callback: (event$: Observable<E>, deps$: Observable<D>) => Observable<unknown>,
      deps: D = [] as never
    ) => {
      const event$ = useMemo(() => createEvent$<E>(), []);
      const deps$ = useMemo(() => createDeps$<D>(deps), []);
    
      useEffect(() => {
        const subscriber = callback(event$, deps$ as Observable<D>).subscribe();
    
        return () => {
          subscriber.unsubscribe();
          event$.complete();
    
          if (deps$) {
            deps$.complete();
          }
        };
      }, []);
    
      useEffect(() => {
        if (deps$) {
          deps$.next(deps);
        }
      }, deps);
    
      return useCallback((e: E) => event$.next(e), []);
    };
    

    And perhaps, it should be called as useEvent$Callback to make it more obvious what this hook is doing.

    Reply
  • Respond to prop changes with side effect
    Respond to prop changes with side effect

    Nov 27, 2019

    Sometimes we want to use an observable not to create state but rather just to perform side effects.

    This is possible using useObservable:

    import React from 'react';
    import { EMPTY } from 'rxjs';
    import { useObservable } from 'rxjs-hooks';
    import { map, mergeMapTo, tap } from 'rxjs/operators';
    
    declare const sideEffect: (number: number) => void;
    
    const MyComponent: React.FC<{ foo: number }> = props => {
      useObservable(
        input$ =>
          input$.pipe(
            map(([foo]) => foo),
            tap(sideEffect),
            mergeMapTo(EMPTY),
          ),
        props.foo,
        [props.foo],
      );
    
      return <div>Hello, World!</div>;
    };
    

    … but it's a bit awkward for a few reasons:

    • We are forced to return an Observable of a certain type (State), when the type doesn't matter to us, since the result is not being used outside of the hook. We need to do mergeMapTo(EMPTY) at the end of the chain, to satisfy the return type.
    • We are forced to provide an initial state, but again we are not using State here.

    What do you think about another hook which is specifically designed for this use case, to avoid the problems above?

    useObservableSideEffect(
      input$ =>
        input$.pipe(
          map(([foo]) => foo),
          tap(sideEffect),
        ),
      [props.foo],
    );
    

    I'm sure we can find a better name… 😄

    feature request 
    Reply
  • Suggestion: use object for `input` instead of tuple
    Suggestion: use object for `input` instead of tuple

    Nov 28, 2019

    When passing in many inputs, it's awkward to remember the indexes inside the input tuple when you're trying to read/destructure a specific input, e.g:

    useObservable(
      input$ => {
        const dispatch$ = input$.pipe(
          map(input => input[5]),
          distinctUntilChanged(),
        );
    
        /* … */
      },
      200,
      [
        props.shouldDisableInfiniteScroll,
        props.isDoneFetching,
        props.fetchDataParams,
        props.fetchDataAndBuildActions,
        props.dispatch,
        props.history,
      ],
    );
    

    If the inputs were represented as an object instead, they would be much easier to destructure.

    useObservable(
      input$ => {
        const dispatch$ = input$.pipe(
          map(({ dispatch }) => dispatch),
          distinctUntilChanged(),
        );
    
        /* … */
      },
      200,
      pick(
        props,
        "shouldDisableInfiniteScroll",
        "isDoneFetching",
        "fetchDataParams",
        "fetchDataAndBuildActions",
        "dispatch",
        "history",
      ),
    );
    
    feature request 
    Reply
  • Feature request: useObservable to return the instant value on Component initialize if Observable has sync value
    Feature request: useObservable to return the instant value on Component initialize if Observable has sync value

    Aug 10, 2020

    In my component, I use an useEffect to do some initialization with side effect.

    const title = useObservable(() => titles$);
    useEffect(() => {
        // do initialization with title
    }, []);
    

    title$ is a BehaviorSubject, so I would like the useEffect block can get the current title value directly, so that I don't need to wait another cycle for the initialization.

    Reply
  • This project is alive?
    This project is alive?

    Jan 28, 2021

    Unfortunately i don't see any activity here :(

    @Brooooooklyn @zry656565

    Reply
  • How to combine observables from different useEventCallback?
    How to combine observables from different useEventCallback?

    May 2, 2019

    Hi, I don't quite get how I can get to work combining together two streams made separately with useEventCallback. Here's what I would like to achieve: I have an animation, that can be controlled in two ways: user can click "run/stop" button and moreover he can control animation rate via slider input. I would like to use values from slider combined with button clicks mapped to "true/false", representing whether animation is running or not. Then I'd like to filter this combined stream based on this value, so in the end animation is triggered only when last click from the button turned it on again (with switchMap to interval() based on slider value). Here's a minimal reproduction of my code: https://codesandbox.io/s/pprzmxy230. How to fix it?

    question 
    Reply
  • why useEventCallback's inputs$ and states$ opposite to their input argument order
    why useEventCallback's inputs$ and states$ opposite to their input argument order

    Jun 20, 2019

      const [onEvent] = useEventCallback((events$, x$, y$) => {
        x$.subscribe(x => console.log('x ', x)) // log: x [false]
        y$.subscribe(y => console.log('y ', y)) // log: y [0]
      }, [0], [false]);
    

    I thinks x to be 0 and y to be false is expected, but useEventCallback give opposite result. Am I doing something wrong.

    versions info: rxjs-hooks: 0.4.3

    good first issue 
    Reply
  • An in-range update of @types/webpack is breaking the build 🚨
    An in-range update of @types/webpack is breaking the build 🚨

    Aug 19, 2019

    The devDependency @types/webpack was updated from 4.32.2 to 4.39.0.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    @types/webpack is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your CircleCI tests were canceled (Details).

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply
  • An in-range update of lint-staged is breaking the build 🚨
    An in-range update of lint-staged is breaking the build 🚨

    Aug 25, 2019

    The devDependency lint-staged was updated from 9.2.3 to 9.2.4.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    lint-staged is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your CircleCI tests were canceled (Details).

    Release Notes for v9.2.4

    9.2.4 (2019-08-25)

    Bug Fixes

    • include renames when getting list of staged files (2243a83)
    Commits

    The new version differs by 1 commits.

    • 2243a83 fix: include renames when getting list of staged files

    See the full diff

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply
  • An in-range update of standard is breaking the build 🚨
    An in-range update of standard is breaking the build 🚨

    Aug 22, 2019

    The devDependency standard was updated from 14.0.0 to 14.0.1.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    standard is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your CircleCI tests were canceled (Details).

    Commits

    The new version differs by 9 commits.

    See the full diff

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply
  • An in-range update of webpack is breaking the build 🚨
    An in-range update of webpack is breaking the build 🚨

    Jul 17, 2019

    The devDependency webpack was updated from 4.35.3 to 4.36.0.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    webpack is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your tests failed on CircleCI (Details).

    Release Notes for v4.36.0

    Features

    • SourceMapDevToolPlugin append option now supports the default placeholders in addition to [url]
    • Arrays in resolve and parser options (Rule and Loader API) support backreferences with "..." when overriding options.
    Commits

    The new version differs by 42 commits.

    • 95d21bb 4.36.0
    • aa1216c Merge pull request #9422 from webpack/feature/dot-dot-dot-merge
    • b3ec775 improve merging of resolve and parsing options
    • 53a5ae2 Merge pull request #9419 from vankop/remove-valid-jsdoc-rule
    • ab75240 Merge pull request #9413 from webpack/dependabot/npm_and_yarn/ajv-6.10.2
    • 0bdabf4 Merge pull request #9418 from webpack/dependabot/npm_and_yarn/eslint-plugin-jsdoc-15.5.2
    • f207cdc remove valid jsdoc rule in favour of eslint-plugin-jsdoc
    • 31333a6 chore(deps-dev): bump eslint-plugin-jsdoc from 15.3.9 to 15.5.2
    • 036adf0 Merge pull request #9417 from webpack/dependabot/npm_and_yarn/eslint-plugin-jest-22.8.0
    • 37d4480 Merge pull request #9411 from webpack/dependabot/npm_and_yarn/simple-git-1.121.0
    • ce2a183 chore(deps-dev): bump eslint-plugin-jest from 22.7.2 to 22.8.0
    • 0beeb7e Merge pull request #9391 from vankop/create-hash-typescript
    • bf1a24a #9391 resolve super call discussion
    • bd7d95b #9391 resolve discussions, AbstractMethodError
    • 4190638 chore(deps): bump ajv from 6.10.1 to 6.10.2

    There are 42 commits in total.

    See the full diff

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply
  • reduce object creation by useMemo
    reduce object creation by useMemo

    Aug 4, 2019

    Hi, thank you for creating this amazing library!

    I've noticed there are some performance improvements can be done by using useMemo in useEventCallback hook. The change basically leverage the lazy evaluations and reduce new object creations at run-time.

    Thank you!

    Reply
  • An in-range update of @types/lodash is breaking the build 🚨
    An in-range update of @types/lodash is breaking the build 🚨

    Aug 27, 2019

    The devDependency @types/lodash was updated from 4.14.137 to 4.14.138.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    @types/lodash is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your CircleCI tests were canceled (Details).

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply
  • Feature Request: need to subscribe action$ when apply useReducer
    Feature Request: need to subscribe action$ when apply useReducer

    Dec 28, 2018

    When I am doing your hire problem 3 and 4: write a autocomplete component. I am using useReducer to manage state. Then I found the useObservable and useEventCallback can not solve the problem properly.

    It's more suitale to subscribe to the action stream when works with useReducer. And I have written a simple custom hook ‘useReducerEffect’ to solve the problem, but still meet some difficulty.

    My idea:

    1. Call the useReducer hook, which return state and dispatch.
    2. Pass state and dispatch and other params into 'useReducerEffect'.
    3. Replace dispatch with dispatchWithEffect for the particular action.
    type EffectFactory<Action, State> =
      (action$: Observable<Action>, state$: Observable<State>) => Observable<any>
    
    const [state, dispatch] = useReducer(reducer, initialState, initalAction)
    const [result, dispatchWithEffect] =
      useReducerEffect(effectFactory, state, dispatch, initalAction, initialResult)
    

    Now we have two versions of dispatch. You can call orignal 'dispatch', but this action will not appear in the effectFactory's action$. And the action dispatched by 'dispatchWithEffect' will appear. The state$ in effectFactory is guaranteed to be up to date. This means when a action comes in effectFactory's action$, this action already passed the reducer and updated the state.

    My examples is here.

    question 
    Reply
  • An in-range update of webpack is breaking the build 🚨
    An in-range update of webpack is breaking the build 🚨

    Aug 13, 2019

    The devDependency webpack was updated from 4.39.1 to 4.39.2.

    🚨 View failing branch.

    This version is covered by your current version range and after updating it in your project the build failed.

    webpack is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

    Status Details
    • ci/circleci: build: Your CircleCI tests were canceled (Details).

    Release Notes for v4.39.2

    Bugfixes

    • fix ProfilingPlugin not ending traces correctly
    Commits

    The new version differs by 38 commits.

    • 7265427 4.39.2
    • 9f27d0c Merge pull request #9559 from jamesgeorge007/feat/refactor-banner-plugin
    • b50a995 Merge pull request #9568 from webpack/dependabot/npm_and_yarn/eslint-plugin-jest-22.15.1
    • 385fe6a chore(deps-dev): bump eslint-plugin-jest from 22.15.0 to 22.15.1
    • 7ea8665 Merge pull request #9566 from timneutkens/fix/profiling-callback-override
    • 069c33a Fix asyncHook callback interceptor for ProfilingPlugin
    • ba56f7e Merge pull request #9564 from webpack/dependabot/npm_and_yarn/acorn-6.3.0
    • bd7655c chore(deps): bump acorn from 6.2.1 to 6.3.0
    • e62b643 Merge pull request #9558 from jamesgeorge007/hotfix/fix-typo
    • d7486fd fix: revert
    • aed5cce minor fix
    • 4f003c2 tweak
    • fa3b3ef refactor
    • 72ee5a3 fix: lint
    • af8906d fix: refactor

    There are 38 commits in total.

    See the full diff

    FAQ and help

    There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


    Your Greenkeeper Bot :palm_tree:

    greenkeeper 
    Reply