React Redux Saga Event Channel Cancel
Asked Answered
A

2

5

Is there any possible way via something like a side effect in Redux Saga to cancel an eventChannel?

Given an eventChannel connecting to an external event/data stream, in this case a Firebase realtime database "child_added" event:

// action
const types = { SYNC: 'SYNC_TODOS' };
function syncTodos(todos) {
    return { types: types.SYNC, todos }
}

// saga
function todosChannel() {
  // firebase database ref
  const ref = firebase.database().ref('todos/');

  const channel = eventChannel(emit => {
    const callback = ref.on('child_added', (data) => {
      emit({ snapshot: data, value: data.val() })
    });

    // unsubscribe function
    return () => ref.off('child_added', callback);
  });

  return channel;
}

function* sync() {
  const channel = yield call(todosChannel);

  try {
    while (true) {
      const { value } = yield take(todosChannel);
      yield put(actions.syncTodos(value));
    }
  }
  finally {
    if(yield cancelled()) {
      channel.close();
    }
  }
}

export default function* rootSaga() {
  yield fork(sync);
}

Is there any way to use a side effective such as fork() with something like takeEvery() to listen for an action to cancel the event channel and stop listening to the Firebase "child_added" event/data stream? Or does this require somehow saving a reference to the channel and executing a cancel() on the channel reference itself?

Thank you for any help you can provide.

Almucantar answered 26/9, 2017 at 14:23 Comment(0)
F
11

You mean this?

function* sync() {
  const channel = yield call(todosChannel);

  yield takeEvery(channel, function*({value}){
    yield put(actions.syncTodos(value))
  }

  yield take('CANCEL_WATCH')
  channel.close();
}

BTW, takeEvery is helper, not effect.

Febrile answered 29/9, 2017 at 7:31 Comment(3)
That is really clean, thank you! If I wanted to conditionally execute the channel creation, I could simply put a yield take('SOME_ACITON') before the yield call(todosChannel)?Almucantar
to alex: yes, take is blocking effect.Febrile
Stuck with yucky while(true) for the whole life until saw this.Andromache
C
6

I had to modify the accepted answer approach a little to catch errors emitted in my channel. I also prefer to handle the cancel in the fork rather than forking to handle values as in the accepted answer.

function* sync() {
  const channel = yield call(todosChannel);

  yield fork(function* () {
    yield take('CANCEL_WATCH')
    channel.close();
  })

  try {
    while (true) {
      const { value } = yield take(channel)
      yield put(actions.syncTodos(value))
    }
  }
  catch (error) {
    yield put(actions.cancelWatch()) // to emit 'CANCEL_WATCH'
    yield put(actions.errorTodos(error))
  }
}
Clop answered 16/2, 2019 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.