Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Redux Saga Event Channel Cancel

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.

like image 214
Alexander Staroselsky Avatar asked Dec 04 '22 21:12

Alexander Staroselsky


2 Answers

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.

like image 112
cyrilluce Avatar answered Dec 11 '22 17:12

cyrilluce


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))
  }
}
like image 22
ccwasden Avatar answered Dec 11 '22 16:12

ccwasden