4
votes

I am toggling visibility of a modal in react based on result of some logic performed in saga middleware by dispatching action from saga.

I went through:

Store

export default function configureStore(preloadedState) {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [..otherMiddleware, sagaMiddleware, ...someMoreMiddlewares];

  const store = createStore({
    // other configuration,
    // middleWares
  })

  sagaMiddleware.run(rootRunner);
  return store;
}

Reducer:

const initialState = {
  activeSwitch: '1',
  modalVisibility: false,
}

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case 'TOGGLE_MODAL':
      return state.set('modalVisibility', !state.get('modalVisibility'));
    case 'UPDATE_ACTIVE_SWITCH':
      // update active switch
    default:
      return state;
  }
}

Action:

export const switchOption = payload => ({
  type: 'SWITCH_OPTION',
  payload,
})

export const toggleModal = () => ({
  type: 'TOGGLE_MODAL',
})

export const updateActiveSwitch = payload => ({
  type: 'UPDATE_ACTIVE_SWITCH',
  payload,
})

Component:

import switchOption from 'action';

function Component(props) {
  return <div onClick={props.switchOpt(somePayloadParameter)} />;
}

const mapDispatchToProps = state => ({
  switchOpt: (somePayloadParameter) => dispatch(switchOption(somePayloadParameter)),
})

export default connect(null, mapDispatchToProps)(Component);

RootSaga:

export default function* rootRunner() {
  yield all([ fork(watcher) ])
}

Saga:

function* worker(payload) {
  console.log('hey');
  yield put({'TOGGLE_MODAL'})
  // Perform some task and wait for modal ok button click
  yield take('MODAL_OK');
  if (taskSuccess) {
  yield put({ type: 'UPDATE_ACTIVE_SWITCH', someValue});
  yield put({ type: 'TOGGLE_MODAL'}};
}

export default function* watcher() {
  while(true) {
    yield actionObj = yield take('SWITCH_OPTION');
    yield call(worker, actionObj.payload);
  }
}

Modal is never visible as 'TOGGLE_MODAL' is being dispatched twice from saga, as a result of watcher calling worker twice.

If I put a debugger just after while(true) { in watcher, on page load, that breakpoint is hit twice.

Even if I remove every line from worker, it is still running twice.

Why is my saga code running twice?


EDIT

Component:

import switchOption from 'action';

function Component(props) {
  return <div onClick={props.switchOpt(somePayloadParameter)} />;
}

const mapDispatchToProps = state => ({
  // switchOption is action from action.js
  switchOpt: (somePayloadParameter) => dispatch(switchOption(somePayloadParameter)),
})

export default connect(null, mapDispatchToProps)(Component);

Redux monitor middleware logs to the console in dev tools following three actions, after executing saga function when it is called onClick for the first time:

  • 'SWITCH_OPTION'
  • 'TOGGLE_MODAL' --> with modalVisibility set to true
  • 'TOGGLE_MODAL' --> with modalVisibility set to false

Now the click on div becomes useless as MODAL never popped up and there is no OK button to click on.

1
Maybe if you add more complete code we can see where the flow is going. As a loose try fore example, I guess in you reducer you have always a "return" statement for each case, otherwise UPDATE_ACTIVE_SWITCH will be executed twice, first when opening the popup and a second time when really updating the switch.Carlos Ruana
@CarlosRuana, I updated the code so that its more complete, I can't seem to identify the bug neither could find a way to debug sagas. Now I am reading examples in github.com/redux-saga/redux-saga/tree/master/examplesHarshvardhanSharma
In the component you have "switchOption: () => dispatch(toggleVisibility())" but I do not see that implementation of toggleVisibility on actions and the relation then to the toggleModal or the reducer. Is there something still missing in that code that you can attach?Carlos Ruana
@CarlosRuana Please look at Edit part of the question. I have updated the original question.HarshvardhanSharma

1 Answers

0
votes

Right now Component is calling props.switchOpt every time it renders. Instead, create a new function that can be passed by reference and is then called with onClick:

function Component(props) {
  return <div onClick={() => { props.switchOpt(somePayloadParameter); }} />;
}