Enable addEventListener callback to do put's
Asked Answered
U

1

7

I need to do a global addEventListener like thing. I am using react native and need to listen for deep link redirect events. I couldn't figure out the redux-saga way to do this. I need to enable a put from the callback within the addEventListener.

I currently do the bad hack of importing store then doing store.dispatch(...) as seen here:

import store from './flow-control'

Linking.addEventListener('url', ({ url }) => store.dispatch(_redir(url)));

Is there a redux-saga way to do this?

I was hoping to put this into a a saga maybe:

function* reduxSaga() {

    const url = yield Linking.addEventListener('url', ({ url }) => ???);

}

Or at the least replace store.dispatch with put like this:

import { put } from 'redux-saga/effects'

Linking.addEventListener('url', ({ url }) => put(_redir(url)));

Whats the right way to do this with redux-saga?

Unset answered 11/2, 2018 at 19:48 Comment(0)
S
7

One possible solution, is to use channels. Here is an example that should work in your case:

import { channel } from 'redux-saga'
import { put, take, race } from 'redux-saga/effects'

const redirectChannel = channel()

export function* startRedirectChannel(id) {
  Linking.addEventListener('url', ({ url }) => redirectChannel.put({
    type: REDIRECT,
    url,
  }))
}

export function* watchRedirectChannel() {
  while (true) {
    const action = yield take(redirectChannel)
    yield put(action)
  }
}

The idea here is that we will push a REDIRECT action on the channel for each url event that is emitted from Linking.addEventListener.

We also have to start another saga function that is listening to each pushed action in a while loop (watchRedirectChannel). Everytime an action has been taken from the channel, we use the normal yield put to tell redux-saga that this action should be dispatched.

Here an example using the eventChannel API:

import { eventChannel } from 'redux-saga'
import { put, take } from 'redux-saga/effects'

export function* watchRedirect() {
  const redirectChannel = eventChannel(emitter => {
    Linking.addEventListener('url', emitter)

    // The subscriber must return an unsubscribe function
    return () => {
      Linking.removeEventListener('url', emitter)
    }
  })

  while (true) {
    const { redirectEvent, cancelEvent } = race({
      redirectEvent: take(redirectChannel),
      cancelEvent: take(ACTION_Y),
    })

    if (redirectEvent) {
      yield put({
        type: REDIRECT,
        url,
      })
    } else {
      redirectChannel.close()
      return
    }
  }
}
Standpoint answered 12/2, 2018 at 7:41 Comment(6)
Very interesting thanks Alex. Is it possible to use eventChannel for this? It says here redux-saga.js.org/docs/advanced/Channels.html for "external events". It sounds like its my case but im not sure. I'm new to channels. Thanks Alex!Unset
It also seems creating a channel like this takes only first buffers just 10 messages - per here - redux-saga.js.org/docs/api - is it possible to have unlimited buffer here?Unset
You can also use the eventChannel API. eventChannel let's you return an unsubscribe function as well an END event. The buffer should be of no concern here, as the watcher immediately takes all incoming REDIRECT action'sStandpoint
Thanks Alex! May you please show me an eventChannel example that spins up when it gets ACTION_X and unsubscribes/remvoes listener on ACTION_Y - im totally lost on the even channelUnset
I updated the answer. For starting the saga with ACTION_X just use takeLatestStandpoint
I have used the technique above. Here is an example that integrates this approach with the app's initialization/termination logic gist.github.com/yaraabdallah10/c10e4a2739874bdb50b40305f3add1d8Protecting

© 2022 - 2024 — McMap. All rights reserved.