3

Hi Stack Overflow community!

I'm looking for a good practice and implementation to wait for a dispatched action in a component.

I know React-Native and Redux-Saga just say: don't wait for it, let the store be updated, and through selectors, your view will re-render.

All good and well, but how to handle things like this:

onPress = (object) => {
    this.props.editObject(object).then(() => { //editObject is an action
        NavigationService.navigateTo(otherScreen); 
    }).catch((error) => {
        // Handle and show validation errors
    }) 
}

Of course in the above example, this crashes, since there is no then or catch, since it is an action and not a promise... So how to realize such a flow?

The only solution I can come up with is to pass a string navigateTo to the action and navigate in the saga itself, which is not clean at all...

Any advice?

EDIT:

I still haven't found a good solution for this problem and I am getting more and more frustrated about the redux-saga middleware.

Let's give a more concrete example: I have a profile & image which needs to be updated (sequentially), in any normal js-framework this would be

try { 
    setLoading(true);
    await updateProfile(...);
    await updateProfileImage(...);
    navigate(...)
} catch (e) {
    //...
} finally {
    setLoading(false);
}

To realize this with redux-saga, it would take me a new store (with profileUpdateDone, profileUpdateFailedStatus, profileImageUpdateDone and profileImageUpdateFailedStatus), a new reducer, new selectors, ...

  • Do the action
  • Update the store
  • Listen in componentDidUpdate for profileUpdateDone or -FailedStatus
  • Handle the error or do the second action
  • Update the store
  • Listen in componentDidUpdate for updateProfileImageDone or -FailedStatus
  • Handle the error or navigate

The store is getting really dirty for adding all that crap. I could introduce a single store, reducer, actions and selectors per single component, but I got 58 components and already 7 stores to manage the global state of the app. It seems unmanageable to have 65 stores, 65 reducers, 65 selector files, etc...

I've been breaking my head into finding some kind of clean design pattern to fix this. It seems like the most basic of basic tasks that any JS-framework should be able to handle easily, but for some reason not in redux-saga.

Am I the only one facing this problem, or is redux-saga only meant for small apps with e.g. no more than 20 screens?

Thomas Stubbe
  • 1,945
  • 5
  • 26
  • 40

2 Answers2

0

You can store one initial state in your reducer say isEditObjectDone:false.

When reducer receives data from action change this to true.

And, In your screen at componentDidUpdate or componentWillReceiveProps

do this,

if(this.props.isEditObjectDone){
   NavigationService.navigateTo(otherScreen); 
}

dont forget to map state to props.

EDIT:

make your action to return a promise,

like this,

editObject = object => dispatch =>
  new Promise((resolve, reject) => {
    //your code and dispatch thing
    // don't return dispatch instead do this
    dispatch({
    type: 'something',
    ...
     });
    if (something) 
      resolve(someData);
    else 
      reject(someError);
  });
Jaydeep Galani
  • 4,842
  • 3
  • 27
  • 47
  • That was my first idea... But... For a simple `call().then().catch()`, this would require: a first action (callAction), a second action (doneAction), a reducer (handleDoneAction), a state (actionDone), a selector (isActionDone), a state (actionErrors), a selector (actionErrors), a componentDidUpdate to handle all this crap which also reverts the actionDone state. Also `componentWillReceiveProps` is deprecated and unsafe, so only `componentDidUpdate` can be used. This seems not the cleanest way to handle this... Do you have any better ideas? – Thomas Stubbe Feb 20 '19 at 10:23
  • This is redux-thunk + adding promises right? We choose to use redux-saga instead of redux-thunk though. I would prefer to keep it at one middleware library. Also, in this case, why not use `Api.doSomething().then().catch()` directly from the component then? According to redux-saga, actions should be plain objects without any data logic – Thomas Stubbe Feb 20 '19 at 10:50
  • for `Api.doSomething().then().catch()`, `.then().catch()` is not default functionality. whatever method you're calling must return promise to have this functionality. so how can we directly achieve? – Jaydeep Galani Feb 20 '19 at 11:00
  • Our Api does return a Promise (it's actually async await). The saga wraps this with a call(api.doSomething, ...) – Thomas Stubbe Feb 20 '19 at 11:28
0

navigateTo is more of a component thing while editObject is a state-wise / saga thing. Saga shouldn't know screen transitions.

So let's introduce a "glue" variable that saga will update once editing is done and which the component can listen to.

// Saga
function* editObject(payload) {
  try {
    // do editing
    yield put(editDone(...)); // done editing
  } catch() {...}
}

// Reducer
let initialState {
  editDone: false, // glue variable
};

objList = (state=initialState, action) => {
  switch(...) {
    case 'EDIT_DONE':
      return {...state, editDone: true }; // update glue variable
  }
}

// Component
componentDidUpdate(prevProps) {
  if (this.props.editDone && !prevProps.editDone) {
    // navigate to other screen
  }
}
Joseph D.
  • 11,804
  • 3
  • 34
  • 67