crimx/observable-hooks

Auto unsubscribe on the element is detached

ArielGueta opened this issue · 5 comments

Thanks for the library.

Let's say I have the following example:

function App() {
  const [show, setShow] = React.useState(true);
  const [text, updateText] = useObservableState<string>(
    text$ => text$.pipe(delay(1000), map(() => Math.random().toString()), finalize(() => console.log('done'))),
    'Init'
  )

  return (
    <section>
      <button onClick={() => setShow((v) => !v)}>Toggle</button>
        {show && <h1 onClick={updateText}>{text}</h1>}
    </section>
  )
}

When we toggle the element the subscription is still alive. The finalize isn't invoked. How do we deal with this situation?

Thanks.

Btw, I also get a type error:

image

crimx commented

When we toggle the element the subscription is still alive.

The observable is auto-unsubscribed when the component unmounts. For your situation the <h1> and the observable should move to a new component. You can also control subscription manually with useSubscription but that's not a good fit for this scenario.

Btw, I also get a type error:

useObservableState<string, React.MouseEvent<HTMLHeadingElement>> or useObservableState<string, unknown> if you want arbitrary argument.

What about passing the show property to useObservableState and it will unsubscribe when it's false and resubscribe when it's true. It's a bit of overhead to create a component for this.

crimx commented

If creating a component is a overhead then it is safe to just leave the observable as it is. There is close to no impact in terms of CPU and memory footprint.

What about passing the show property to useObservableState and it will unsubscribe when it's false and resubscribe when it's true.

Patterns like this will create unnecessary complication IMO. You may create a custom hook for this though. Do note that it may cost more if you keep unsubscribing and resubscribing.

Ok, thanks. Last question:

What about a use case where I have a BehaivorSubject? In this case, I expect that useObservable will take the initial value synchronously and use it instead of passing a "redundant" initial value and cause an additional re-render. What do you think?

It's common usage in state management libraries such as Akita.

crimx commented

Unfortunately as I had explained in the docs performing synchronous side effects is not safe in concurrent mode.

It might be okay if just a Behaviour Subject but users may create all kinds of observable pipelines. If you have for example an initial remote data fetching, this could be triggered multiple times if the subscription happens in render phase.