thodwris
thodwris

Reputation: 1377

How to stop/cancel redux saga when navigating

I am developing an app in react native and I need to stop/cancel redux saga when navigating between screens. How can I accomplish that?

I need this because I want to to avoid the lag when transitioning. You can see the video to get better my point https://streamable.com/45qsa

My code is below.

MindfulnessScreen.js

class MindFulness extends Component {


  componentDidMount() {
    this.props.dispatch(getMindFulness());
    this.props.dispatch(toggleBottomBar(true));
  }


  render() {
    const { isFetchingData, mindfulnessData, isLoggedIn, userType } = this.props;
    const header = mindfulnessData.header;
    const subHeader = mindfulnessData.subheader;
    const imageBanner = FILES_URL + mindfulnessData.image_banner;
    const mindFulnessDatas = mindfulnessData.children;
    return (
      <View style={{ flex: 1, backgroundColor: '#1F1F20' }}>
        {isFetchingData && <LoadingIndicator />}
        {/* <BottomBar screen={'MindFulness'} navigation={this.props.navigation} /> */}
        <ScrollView style={{ flexGrow: 1, marginBottom: 35 }}>
          {!isFetchingData && <FastImage
            style={{
              width: '100%',
              height: 137,
              display: "flex",
              alignItems: "center",
            }}
            resizeMode={FastImage.resizeMode.cover}
            source={{ uri: imageBanner }}
          >
            <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', paddingLeft: 30, paddingRight: 30 }}>
              <Text style={{
                textAlign: 'center',
                fontSize: 20,
                color: '#FFFFFF',
                fontFamily: Theme.FONT_BOLD
              }}>{header}</Text>
              <Text style={{
                textAlign: 'center',
                fontSize: 14,
                paddingTop: 8,
                color: '#FFFFFF',
                fontFamily: Theme.FONT_MEDIUM
              }}>{subHeader}</Text>
            </View>
          </FastImage>}

          {this.renderData(mindFulnessDatas)}

          {!isFetchingData && isLoggedIn && userType == 0 && <View style={{
            width: width,
            height: 200,
            marginBottom: 30,
            borderRadius: 12,
            shadowRadius: 16,
            shadowOffset: { width: 0, height: 8 },
            shadowColor: "black",
            shadowOpacity: 0.47,
            elevation: 2
          }}
          >
            <FastImage style={{ width: '100%', height: '100%' }} source={unlockActivitiesBannerImage}>
              <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
                <Text style={{
                  fontSize: 20,
                  color: '#FFFFFF',
                  textAlign: 'center',
                  position: 'absolute',
                  top: 40,
                  fontFamily: Theme.FONT_BOLD
                }}>{'All Synesthesia Meditations \n 7 days for free'}</Text>

                <CustomButton
                  disabled={false}
                  style={{
                    height: 50,
                    alignSelf: 'center',
                    alignItems: 'center',
                    justifyContent: 'center',
                    marginTop: 45,
                    width: 230,
                    borderRadius: 45,
                    backgroundColor: '#25B999',
                    opacity: 1
                  }}
                  title="Start free trial"
                  onPress={() => {
                    this.props.dispatch(setMenuItem('7 days for free'))
                    this.props.navigation.navigate('Pricing')
                  }}
                />
              </View>
            </FastImage>
          </View>}


        </ScrollView>
      </View>
    )
  }
}

function mapStateToProps(state) {
  return {
    isFetchingData: state.mindfulnessReducer.isFetchingData,
    mindfulnessData: state.mindfulnessReducer.mindfulnessData
  }
}

export default connect(mapStateToProps)(MindFulness);

AwarenessScreen is similar to MindfulnessScreen.js

MindFulnessAction.js

import { ActionTypes } from '../constants/constants'

export function getMindFulness() {
  return {
    type: ActionTypes.GET_MINDFULNESS,
    payload: {}
  }
}

mindulnessReducer.js

import { ActionTypes } from '../constants/constants'

const initialState = {
  error: false,
  isFetchingData: false,
  mindfulnessData: [],
};

export const mindfulnessReducer = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.GET_MINDFULNESS:
      return {
        ...state,
        isFetchingData: true
      }
    case ActionTypes.GET_MINDFULNESS_SUCCESS:
      return {
        ...state,
        isFetchingData: false,
        mindfulnessData: action.payload.node
      }
    case ActionTypes.GET_MINDFULNESS_FAIL:
      return {
        ...state,
        error: true,
        isFetchingData: false
      }
    default:
      return state
  }

}

api.js

let commonHeaders = {
  'Content-Type': 'application/json',
}

export const getMindFulness = (token) => fetch(`${baseUrl}node/337?token=${token}`, {
  method: 'GET',
  headers: {
    ...commonHeaders,
  },
}).then(response => response.json());

mindFulnessSaga.js

import { AsyncStorage } from 'react-native';

import { put, call, select } from 'redux-saga/effects'
import { ActionTypes } from '../constants/constants'
import { getMindFulness, getMindFulnessAnonymous } from '../api/api'

export const getMindfulnessData = (state) => state.mindfulnessReducer.mindfulnessData;

const MindFulnessSaga = function* (action) {
  const token = yield AsyncStorage.getItem('token');
  const mindfulnessData = yield select(getMindfulnessData);

  // if (mindfulnessData.length == 0) {
  if (token !== null) {
    const dataObject = yield call(getMindFulness, token);
    if (dataObject.status.success) {
      yield put({
        type: ActionTypes.GET_MINDFULNESS_SUCCESS,
        payload: {
          ...dataObject
        }
      })
    }
    else {
      yield put({
        type: ActionTypes.GET_MINDFULNESS_FAIL
      })
    }
  }
  else {
    const dataObject = yield call(getMindFulnessAnonymous);
    yield put({
      type: ActionTypes.GET_MINDFULNESS_SUCCESS,
      payload: {
        ...dataObject
      }
    })
  }
  // }
}

export default MindFulnessSaga

rootSaga.js

import { takeLatest } from 'redux-saga/effects'
import { ActionTypes } from '../constants/constants'
import MindFulnessSaga from './MindFulnessSaga'
import BeingAwareSaga from './BeingAwareSaga'


const rootSaga = function* () {
  yield takeLatest(ActionTypes.GET_MINDFULNESS, MindFulnessSaga)
  yield takeLatest(ActionTypes.GET_BEINGAWARE, BeingAwareSaga)
}

export default rootSaga

Any recommendations?

Thank you

Upvotes: 1

Views: 5265

Answers (1)

NoriSte
NoriSte

Reputation: 3711

My original answer (I left it below) wasn't correct. I made a mistake while talking about the takeLatest effect because, as the docs say

Spawns a saga on each action dispatched to the Store that matches a pattern. And automatically cancels any previous saga task started previously if it's still running.

So the previous MindFulnessSaga/BeingAwareSaga are automatically canceled by the takeLatest effect. That doesn't mean that the AJAX calls are canceled too if you want to cancel the previous AJAX calls you need to manage the cancellation your own.

You can manage a saga cancellation by putting all its code in a try/finally and manage it in the finally block

import { cancelled } from 'redux-saga/effects'
function* MindFulnessSaga() {
  try {
    // ... your code
  } finally {
    if (yield cancelled())
      // the saga has been cancelled, cancel the AJAX request too
  }
}

That said: the lag on navigation could be related to the fact that you don't cache the AJAX response (but you did it at the beginning since you have commented the // if (mindfulnessData.length == 0) { block).



ORIGINAL ANSWER

You need to:

  • replace the takeLatest with a custom infinite loop (docs)
  • fork (docs) the different sagas so you can cancel (docs) them later
  • cancel the forked sagas when the user navigates to another screen

Look at this answer of mine for a step-by-step explanation.

Let me know if you need more help 😉

Upvotes: 5

Related Questions