2
votes

RxJS has a nifty function, fromCallback that takes a function whose last parameter is a callback and returns an Observable. And I want to combine that with React's setState function so that I can do something analogous to:

const setState = Rx.Observable.fromCallback(this.setState);
setState({ myState: 'Hi there!' }).concat(....)

so that any operations chained to setState are guaranteed to happen after the state has been set and, most importantly, that setState isn't invoked until there's an active subscriber.

What I noticed though is that even without a subscribe, setState is being called right as it's defined and setting the state of my component. So if I have:

networkSignal.flatMap((x) => {
    return setState({ myState: 'test' });
});

the function setState is immediately invoked but the observer it producers won't send a next until there's a subscriber. What I want is for the function to only invoke when there's a subscriber.

Looking into the source you can see that RxJS returns a function that when executed, creates an observable but immediately invokes the function - the callback argument.

1
Hey, this is interesting - what is your use case, or why do this? I would like to know your thinking to come up with this. Thanks!james emanon
I'm building a React port of github.com/akiran/react-slick and before each slide change, I set the state of which slide is currently focused on. So I need to ensure that before I begin the animation, that the state has been set. So using defer and fromCallback as was suggested in the answer, I do something like Rx.Observable.defer(() => setState)) which first sets a function to defer, and calls the observable function created earlier from fromCallbackbarndog
@jamesemanon Speaking a bit more generally, any time you want to set state and then perform an action with the assurance that it's been set, and you also want to use observables, this is the way to go. The React docs talk about doing the same thing using the setState callback, this is really an observable version of doing that. Also, because of the nature of fromCallback, the returned function is executed immediately, the value just doesn't propagate until there's a subscriber. In the case of setState, as soon as it's called it sets the state, regardless if there's a subscriber.barndog
^ That was the real issue I was trying to solve. For example, look at this: setState({ someState: 'hi' }).concat(Rx.Observable.just(42)). I, the reader of this code would assume that when subscription began that setState would be called and so on and so forth. However, it's called on the observables creation, not on it's subscription. So if you create an observable and store it away for later use, the state gets set, not as part of the observer chain.barndog
Of course, glad to help. One word of warning though, if you're going to use this method to turn setState or really anything else into an observable so that you delay the function execution until subscription, in defer make sure you return an observable i.e. Rx.Observable.defer(() => { return setState({ state: 1 }); }. With es6 it's implicit in one line functions but I just ran into an issue with not returning inside the defer. Threw me for a loop.barndog

1 Answers

3
votes

fromCallback returns a function which, when executed, returns an observable. That observable is where the asynchronous result(s) of the function call will flow.

To delay the execution of the function, you can make use of .defer. For instance:

const setState = Rx.Observable.fromCallback(this.setState);
const deferred$ = Rx.Observable.defer(function (){return setState({ myState: 'Hi there!' }).concat(....)});
// Later on
deferred$.subscribe(...)

Question whose answers used the same technique were asked here and here