gyc
gyc

Reputation: 4360

Simple counter doesn't stop incrementing inside functional component with useEffect hook

I trying to use rxjs inside a functional component's useEffect Hook.

I believe useEffect can be used just like componentDidMount and componentDidUpdate.

 const Counter = () => {
  const [state, setState] = useState({counter: 0});
  useEffect(() => {
    subscriber.subscribe((value) => {
        let { counter } = state;
        counter += value;
        setState({ counter });
    });
  });
  return (
    <div>
      <span>Counter: {state.counter}</span>
      <Crementer />
    </div>
  );

The subscriber is a simple rxjs subject

const subscriber = new BehaviorSubject(0);

There's also a component to increment / decrement the counter

const Crementer = () => {
  return(
    <>
      <button onClick={() => subscriber.next(1)}>+</button>
      <button onClick={() => subscriber.next(-1)}>-</button>
    </>
  )
};

(I tried counterService.send(1) as well)

The problem is as soon as I click + or - the counter increments or decrements continuously.

You can see the behavior here: https://stackblitz.com/edit/react-9auz4d

Maybe because useEffect runs also on update? It will probably work in a class component with a componentDidMount() ?

Upvotes: 0

Views: 1086

Answers (1)

martin
martin

Reputation: 96899

Hooks are invoked on every update: https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update

So you could store the subscription returned form subscribe call and then keep it in another useState. On every effect invocation you'd just check whether it's set or not. Btw, you should also return a teardown function from this effect so that's where you'd need the subscription as well.

Or eventually you can use the second parameter to useEffect to run it just once (How to call loading function with React useEffect only once):

useEffect(() => {
  const subscription = observable.subscribe((value) => {
    let { counter } = state;
    counter += value;
    setState({ counter });
  });

  return () => subscription.unsubscribe();
}, []);

Upvotes: 1

Related Questions