crimx / observable-hooks

⚛️☯️💪 React hooks for RxJS Observables. Concurrent mode safe.
https://observable-hooks.js.org
MIT License
1.02k stars 44 forks source link

[Bug]: `useObservable*` hooks does not work when observed value is a `Map` instance #125

Closed nichita-pasecinic closed 11 months ago

nichita-pasecinic commented 11 months ago

In case a JS Map is used as value for BehaviorSubject the value provided from useObservable* hooks will be stale.

Try:

const subject = new BehaviorSubject(new Map());

// subscription outside React (works as expected - emitted 4 times)
subject.subscribe((map) => {
  console.log('map is:', map.keys());
});

setTimeout(() => {
  subject.next(subject.value.set(1, 1));
  setTimeout(() => {
    subject.next(subject.value.set(2, 2));
    setTimeout(() => {
      subject.next(subject.value.set(3, 3));
    }, 1000);
  }, 1000);
}, 1000);

// define react component
const Component = () => {
  const map = useObservableEagerState(subject);
  console.log('map keys: ', map.keys()); // <-- it will NOT trigger rerender 4 times
  return <></>
}
crimx commented 11 months ago

useObservableState and useObservableEagerState follow the React useState convention. If you need to trigger rendering without changing the value, use useSubscription + useRef instead.

nichita-pasecinic commented 11 months ago

@crimx thanks (I thought that calling .next on subject will trigger a re-render), how would I do it with useSubscription + useRef ?

I'd really appreciate if you helped me.

crimx commented 11 months ago

Calling .next will trigger the observer callback. useObservableState and useObservableEagerState consume the value as React state, so no re-rendering is triggered.

To force update, you can use a very simple hook like useUpdate

import { useUpdate } from "react-use";
import { useSubscription } from "observable-hooks";

const subject = new BehaviorSubject(new Map());

export const Comp = () => {
  const update = useUpdate();
  useSubscription(subject, update);
  console.log('map keys: ', subject.keys());
}