re-rxjs / react-rxjs

React bindings for RxJS
https://react-rxjs.org
MIT License
550 stars 19 forks source link

bind hook only firing once with scan and aggregated map #297

Closed MaxTwentythree closed 1 year ago

MaxTwentythree commented 1 year ago

Hi,

I'm trying to implement a stream that collect items from other streams into a map / object. But when I then bind the stream for the collected map I do not receive all updates to the stream.

I made a simple example to showcase that:

const $counter = interval(1000);

export const [useCounterMap, counterMap$] = bind(
  $counter.pipe(
    scan((map, b) => {
      map[String(b)] = b;
      return map;
    }, {})),
  {}
);

Somewhere in a React component:

const counter = useCounterMap();

useEffect(() => {
  console.log(counter, '(counter)')
}, [counter]);

In the logs I receive the following:

{} (counter)
{0: 0} (counter)

And that's it .. So the init value and the first timer event are received successfully, but then it stops. I'm pretty new to rxjs, so maybe I'm doing sth wrong or overlooking sth, but shouldn't the effect keep logging the counter-map with each second? I do see the full map with every update when I directly subscribe to counterMap$

MaxTwentythree commented 1 year ago

Oooh, of course right after creating this issue, I had the idea that the mutability of the map might be the problem.

So with this:

  scan((map, b) => {
    return {...map, [String(b)]: b};
  }, {})),

It works! It kinda makes sense..

I'm not super happy creating a new map on every event though .. is there another way, or am I approaching this completely wrong?

voliva commented 1 year ago

Yes, this is not something that React-RxJS does, but it comes from React itself.

Try this sandbox -> https://stackblitz.com/edit/react-ts-dwqy2x?file=App.tsx,index.tsx

export default function App() {
  const [values, setValues] = useState([]);

  return (
    <div>
      <button
        onClick={() =>
          setValues((v) => {
            v.push(Math.random());
            return v;
          })
        }
      >
        Add
      </button>
      {values.map((v, i) => (
        <div key={i}>{v}</div>
      ))}
    </div>
  );
}

If you click Add, nothing really happens. The values are being added to the array, but React detects it's the same array referentially, so it won't update the component. If you recreate a new array inside setValues, then it will start working.

The observables do create a new emission, so you can still use them to compose other state (as long as you keep it in mind... Operators like distinctUntilChanged, pairwise, etc. won't work as expected). Something I sometimes do is only recreate the map/set/array at the very bottom of the state tree, right before using it in the react components.

MaxTwentythree commented 1 year ago

Thanks for your quick answer! It does make sense and made it clearer to me! The issue can be closed. Thank you :)