Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Observables still use old snapshots. #3404

Closed
mvarble opened this issue Apr 13, 2019 · 4 comments
Closed

Custom Observables still use old snapshots. #3404

mvarble opened this issue Apr 13, 2019 · 4 comments

Comments

@mvarble
Copy link

mvarble commented Apr 13, 2019

What is the current behavior?
Im creating an Observable from the redux store, as follows.

// store
import { createStore } from 'redux';
const reducer = (state, { type, data }) => (
  (type === 'CHANGE_DATA') ? { data } : state
);
const store = createStore(reducer, { data: 'a' });

// observable
import { Observable } from 'rxjs';
const state$ = Observable.create((observer) => {
  const unsubscribe = store.subscribe(()=>observer.next(store.getState()));
  observer.next(store.getState());
  return unsubscribe;
});

From here, I produce a new observable, filtering out any of those where the state doesn't change

import { pairwise, filter, map } from 'rxjs/operators';
const uniqueState$ = state$.pipe(
    pairwise(),
    filter(statePair=>statePair[0].data !== statePair[1].data),
    map(statePair=>statePair[1]),
);

This does in fact produce the results I want when the subscriptions are nice.

// our listener will append each unique letter to some string
let string = '';
uniqueState$.subscribe((state)=>{
  string = string + state.data;
});

// perform it on the following data: gives 'bcbca' as expected
const DATA = [ 'a', 'b', 'c', 'c', 'c', 'b', 'c', 'c', 'a' ];
DATA.forEach((data, i)=>{
  store.dispatch({ type: 'CHANGE_DATA', data })
});

To the contrary, if I put an action that doesn't change the state, I will get a RangeError, despite the fact that this listener is subscribed to the Observable corresponding to unique updates.

// our listener will append each unique letter to some string and make a worthless dispatch
let string = '';
uniqueState$.subscribe((state)=>{
  string = string + state.data;
  dispatch({ type: 'NO_CHANGE_DATA' });
});

// perform only one update (recall state.data is currently 'a')
store.dispatch({ type: 'CHANGE_DATA', data: 'b' });

What is the expected behavior?

If I am not missing something here, I believe that since the 'NO_CHANGE_DATA' dispatch is only called in the listener for the Observable corresponding to unique updates, it should have only been dispatched once in the example above.

Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?

My package.json says that I have redux ^4.0.1

@timdorr
Copy link
Member

timdorr commented Apr 13, 2019

Why aren't you using the built-in observable() on your store? It's well-tested and gives you an Observable store.

@mvarble
Copy link
Author

mvarble commented Apr 13, 2019

Honestly, because I was aware of the functionality! I didn't see it in the docs, but I now see it in the code for the store. Thanks!

@mvarble mvarble closed this as completed Apr 13, 2019
@mvarble
Copy link
Author

mvarble commented Apr 13, 2019

I am now using the built-in observable(), but it still gives me the error. For example, I would expect this code to work,

import { Observable, from } from 'rxjs';
import { createStore } from 'redux';
import { pairwise, filter, map } from 'rxjs/operators';
import $$observable from 'symbol-observable';

// redux store
const reducer = (state, { type, data }) => (
  (type === 'CHANGE_DATA') ? { data } : state
);
const store = createStore(reducer, { data: 'a' });

// create the observable of the state
const state$ = Observable.create(store[$$observable]().subscribe);
const uniqueState$ = state$.pipe(
  pairwise(),
  filter(statePair => statePair[0].data !== statePair[1].data),
  map(statePair => statePair[1]),
);

// test
uniqueState$.subscribe((state) =>{
  console.log(state);
  store.dispatch({ type: 'NO_CHANGE_DATA' });
});
store.dispatch({ type: 'CHANGE_DATA', data: 'b' }));

We see that 'NO_CHANGE_DATA' is consistently being dispatched. I don't see why this is the case, because this means that uniqueState$ is consistently firing, though the state has not changed. In fact, when I log inside one of the operators, I see that I get a repeat of the previous pair. For example, changing the code above to:

state$.pipe(
  pairwise(),
  filter(statePair => {
    console.log(statePair);
    return statePair[0].data !== statePair[1].data;
  }),
  map(statePair => statePair[1]),
)

allows me to see that the pair is consistently [ { data: 'a' }, { data: 'b' } ].

In short, I expect that state$ would have fired 'a' -> 'b' -> 'b', where the first is from the construction, the second is from the 'CHANGE_DATA' dispatch, and the third is from the NO_CHANGE_DATA dispatch.
Meanwhile uniqueState$ would have just done 'b'.

@mvarble mvarble reopened this Apr 13, 2019
@thorn0
Copy link
Contributor

thorn0 commented Apr 26, 2019

Might be a bug inside pairwise because it's totally reproducible without Redux:

import { Observable } from "rxjs"; // version 6.5.1
import { pairwise, filter, map, tap, take } from "rxjs/operators";
import { EventEmitter } from "events";

const emitter = new EventEmitter();

const state$ = Observable.create(observer => {
  observer.next({ data: "a" });
  emitter.on("action", data => {
    observer.next({ data });
  });
});

const uniqueState$ = state$.pipe(
  tap(state => console.log("tap1:", state)),
  pairwise(),
  tap(statePair => console.log("tap2:", statePair)),
  filter(statePair => statePair[0].data !== statePair[1].data),
  map(statePair => statePair[1]),
  take(4)
);

// test
uniqueState$.subscribe(state => {
  console.log(state);
  emitter.emit("action", "b");
});

emitter.emit("action", "b");

Output:

tap1: { data: 'a' }
tap1: { data: 'b' }
tap2: [ { data: 'a' }, { data: 'b' } ]
{ data: 'b' }
tap1: { data: 'b' }
tap2: [ { data: 'a' }, { data: 'b' } ]
{ data: 'b' }
tap1: { data: 'b' }
tap2: [ { data: 'a' }, { data: 'b' } ]
{ data: 'b' }
tap1: { data: 'b' }
tap2: [ { data: 'a' }, { data: 'b' } ]
{ data: 'b' }
tap1: { data: 'b' }
tap2: [ { data: 'a' }, { data: 'b' } ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants