DEV Community

Cover image for Turn a Stream of Objects into an Object of Streams

Turn a Stream of Objects into an Object of Streams

Kostia Palchyk on September 18, 2020

Hi, fellow RxJS streamer! πŸ‘‹ Today I want to share a JS/TS package that allows you to access props of objects on Observables: source$.subscribe(...
Collapse
 
fkrasnowski profile image
Franciszek Krasnowski

Does it play nicely with the <$> component? It would be a great combination. It’s a similar idea to Hookstate. And what about cats and dogs Subject ? and .next method?

Collapse
 
kosich profile image
Kostia Palchyk RxJS

Hi, Franciszek!

Does it play nicely with the <$> component? It would be a great combination.

Great question! Especially since I haven't tried it yet with <$> myself! πŸ˜„ (the idea came up for use in recksjs framework) Now I did! Here's an example:

function getStatus() {
  return ajax.getJSON<{ message: string }>('https://api.github.com/status');
}

function App(){
  const { message } = proxify(getStatus());
  return <$>{ message }</$>
}

^ online playground β€” I've added two precautions in comments, please be advised!

And what about Subject ? and .next method?

Hm, I haven't considered this before πŸ€” The original idea was to provide a selector, independently from provider. And the .next method would be lost after first subproperty access, e.g. stream.a β€” since it's map-ped under the hood. Though having the initial Subject, one can still do:

const state = new Subject<{ a: number, b: number }>();
const { a, b } = proxify(state);
// consume a, b in some way, and then
state.next({ a: 1, b: 2 });

THEORETICAL PART

Yet there's something to be discovered w/ state management, like:

// !pseudocode!
const [in, out] = store({ a: { b: 1 });
in.a.b.subscribe(console.log); // log 1
out.a.b = 2; // next-s to the store, that leads to logging 2

Quite interesting concept, need to think & play around w/ this! πŸ€“

EOF THEORETICAL PART


Let me know what you think!

P.S: And thx for Hookstate, didn't know about it!

Collapse
 
kosich profile image
Kostia Palchyk RxJS

THEORETICAL UPDATE:

// create a state
const [read, write, reset] = createState({ a: { b: { c: 1 } } });

// listen to state changes
read.a.b.c
  .subscribe(c => console.log('C:', c)); // > C:1

// write to the state
write.a = { b: { c: 2 } }; // > C:2
write.a.b = { c: 3 };      // > C:3
write.a.b.c = 4;           // > C:4

// reset the state
reset({ a: { b: { c: 42 } } }); // > C:42

~ Well typed, though has bugs πŸ™‚
Heres a playground: stackblitz.com/edit/rstate?file=in...

Thread Thread
 
fkrasnowski profile image
Franciszek Krasnowski

Wow, it looks like MobX now 🀯. I checked the code. What about changing the approach? Instead of chaining observables, we can chain the properties names and then just pluck them for subscribing. I made the demo. I also used Immer to deliver next state πŸ˜›

const state = store({ a: 1, b: { c: 2 } })

state.subscribe(console.log) // Logs: { a: 1, b: { c: 2 } } | { a: 1, b: { c: 7 } } | { a: 1, b: { c: 100 } }
state.b.c.subscribe(console.log) //Logs: 2 | 7 | 100
state.b = { c: 7 }
state.b.c = 100
Thread Thread
 
kosich profile image
Kostia Palchyk RxJS

Awesome! I especially like that you've united read & write: I wanted to do it too! (but reused rxjs-proxify cz of already implemented TypeScript)

And I agree, a single pluck is nicer & more performant, though we might need a custom pluck operator if we want to support a.b.c() β€” since pluck won't keep the call context: this will be lost, not sure if that's a big deal

I still have mixed feelings regarding whether pushing to state should be a.b.c = 1 or a.b.c.next(1) β€” latter is uglier, I admit, though it has two benefits:

  1. it allows to easily set root state a.next({ b: { c: 1 } })
  2. it gives some symmetry to the API: callback effect subscription on one side, and fn call to create effect on the other (a.b = 1 is not obviously effectful)

Regardless of implementation details, do you think such approach could be useful as a package? I think in Recks it can be handy, maybe in <$> too, not sure where else..

Will try to compile a unified solution early next week πŸ‘€

Thread Thread
 
fkrasnowski profile image
Franciszek Krasnowski • Edited

I don't get the first point. Which this will be lost? Help me understand πŸ˜…. I updated the demo with apply trap and everything seems ok to me. Besides your proxify does not keep the this of the root object if that's what you mean

When it comes to = vs .next: IMHO = with a company of .next or just .next. Keeping only = as you just said disallows setting the root state and makes the proxy less usable in higher-order scenarios like passing the chain as a prop to further set the next value.

Could it be handy as a package? Using RxJS as a standalone state manager is very rare these times. Subjects in comparison to their relatives from MobX seems poor. Although mixing MobX with RxJs feels a little cumbersome due to the very different subscription fashion. Maybe it would be better to create a store by nesting observables as MobX does?