1

Problem

public componentDidMount() { 

    // after component mounts I want to get updates from RX source
    const that = this;
    SomeExternalObservable.subscribe((value) => {
        that.setState((prev, props) => ({
            currentValue: value
        });
    });

}

In some cases, I get warning Cannot update during an existing state transition” React with Observable.

I checked SO for answers to that problem, but most of them just suggested to move my code to componentDidMount.

What happens here

The problem occurred as well when using libraries like https://github.com/jayphelps/react-observable-subscribe.

  • @simonbuchan Does the problem occur if you use another library which I created https://github.com/mmiszy/react-with-observable ? The difference is, it uses `create-subscription` package from Facebook, which is meant deal with sync and async Observables. – Michał Miszczyszyn Jul 02 '18 at 18:25

2 Answers2

2

An alternative would be to use props instead of setState.

For managing React state with Rx I prefer to use the mapPropsStream recompose helper. It can be a bit confusing if you don't tend to use HOCs, but it is actually pretty simple.

const withSomeExternalValue = mapPropsStream(props$ =>
    Observable.combineLatest(
        props$,
        someExternal$.startWith(null),
        (props, someExternalValue) => ({...props, someExternalValue})
    )
);

const MyEnhancedComponent = withSomeExternalValue(MyComponent);

MyEnhancedComponent is a new Component type. When it will mount, it will also subscribe to the external Observable. Any emitted value will render the component with the new value as prop.

Additional explanation and notes:

  • The props$ is an Observable of props. Any change of a prop will result in a new item emitted for prop$

  • The returned Observable is a new props Observable that its emitted items will be used to render the enhanced component.

  • The .startWith(null), is to make sure the enhanced component will be rendered before the first value of someExternal$ is emitted. In this case, the someExternalValue prop will start with null

  • withSomeExternalValue HOC can be reused if these external values are needed by another component. withSomeExternalValue(MyAnotherComponent)

  • The mapPropsStream can be used with other Observable implementation - so it should be configured. Here is how to use it with Rx -

Can be configured globally:

import rxjsconfig from 'recompose/rxjsObservableConfig'
setObservableConfig(rxjsconfig)

Or by using mapPropsStreamWithConfig:

import rxjsConfig from 'recompose/rxjsObservableConfig'
const mapPropsStream = mapPropsStreamWithConfig(rxjsConfig)
ZahiC
  • 13,567
  • 3
  • 25
  • 27
  • I like approach, but I always considered props as a bespoke external interface of the component. Changing them from inside component seems bit dirty. – Lukasz Marek Sielski Nov 23 '17 at 11:41
1

I post the question with the answer as it seems allowed, supported here https://meta.stackexchange.com/questions/17463/can-i-answer-my-own-questions-even-if-i-knew-the-answer-before-asking; spent some time to find the issue and couldn't find similar SO post, so I hope it will help someone.

Solution

Because we can't expect the subscription to be triggered asynchronously working solution is to force it, i.e. by using Scheduler:

public componentDidMount() { 

    // after component mounts I want to get updates from RX source
    const that = this;
    SomeExternalObservable
        .subscribeOn(Scheduler.asap) // here
        .subscribe((value) => {
            that.setState((prev, props) => ({
                currentValue: value
            });
        });

}

Why

Wrap up question

Is there React point of view right way to fix that issue, different than solution presented?