brianegan / bansa

A state container for Java & Kotlin, inspired by Redux & Elm
MIT License
444 stars 29 forks source link

If I feel like I want to dispatch from my Reducer - what is the correct thing to do instead? #20

Closed DavidMihola closed 7 years ago

DavidMihola commented 8 years ago

Hi Brian,

I keep running into situations where my first impulse would be to dispatch a new Action to my store from within my Reducer. But since I don't even have a reference to the store there and since dispatching from the reducer is very much discouraged in Redux I suppose that's not the way to go in Bansa either. Still I can't figure out what to do instead.

For example: I try to load a list of images from my API but the request fails because I am not logged in and so the login form is displayed instead. The login request succeeds and LOGIN_SUCCESS is dispatched. Since I have also stored (something like) retryImageListRequest = true in my state I could now dispatch another LOAD_IMAGE_LIST and load the images in the appropriate Middleware. Except that would mean I would dispatch an Action from within my Reducer.

Unfortunately, I am not only new to Bansa but to similar frameworks as well (I've only done some Elm, and that was only with synchronous computations), so I can't make much sense of articles like this one:

Now you know that this is frowned on. You know that if you have enough information to dispatch an action after the reducer does its thing, then it is a mathematical certainty that you can do what you want without dispatching another action.

I would be very grateful for any suggestions!

brianegan commented 8 years ago

Hey David! On vacation for a couple of days, but I'll get back to ya when I'm back home :)

DavidMihola commented 8 years ago

Oh, wait, I think I got it! It just added another Subscriber to my store, immediately after creating it in my Application:

store?.subscribe {
    if (store!!.state.shouldReloadVotingImages) {
        store!!.dispatch(FETCH_VOTING_IMAGES)
    }
}

Is this what you would suggest, too?

brianegan commented 8 years ago

Hey, thanks for the patience, just got back y'day!

The solution you proposed would totally work, but feels a bit strange to me, so I might need just a bit more info. I'm wondering if the problem is "one step up the stack." Quick q: Under what case would you try to fetch images before you're logged in? Why would that be necessary?

I'm wondering if you could do something like this:

  1. Check logged in state.
    • If Logged in, dispatch a NAVIGATE_TO_HOME (an example, should be wherever the images are needed)
    • If not logged in, dispatch NAVIGATE_TO_LOGIN
      • After Login Successful, dispatch LOGIN_SUCCESS (which could store the user's credentials in the state object as well as setting the "current route" of the application to the HOME screen).
  2. On home screen, make the request there.

In my opinion, it seems odd that you'd try to fetch images when you know the user isn't logged in, knowing that would cause an API issue. That's what I mean by "feels like it's one step up the stack."

Would that work, or is there no way of telling whether or not the user is logged in before dispatching the FETCH_VOTING_IMAGES action?

DavidMihola commented 8 years ago

Hi,

thanks a lot for the reply and don't worry about the delay!

Yes, you're right - the example I gave in my question is a bit contrived. It could still happen, though, if the the login session ID expired on the server while the app still thought the user was logged in.

To give a another example - and a bit more context: I am currently trying to adapt one of our apps to Bansa - this one, in case you're interested - not for production, just because I know the current implementation quite well and could then compare the results. Now, this app has a ViewPager on the main screen: For some of the pages the content is the same whether you're logged in or not. On other pages, anonymous users get some preview data, while logged in users get access to the real data. Which means that after logging in anywhere in the app, I would like to reload all the real data for all pages in the ViewPager. And this should be triggered when/after the application state was updated with the user's credentials, as you suggest.

So, this is another case where a LOGIN_SUCCESS should directly trigger a FETCH_VOTING_IMAGES, without any intervening View changes.

But your solution - navigate first, load after - would probably for other cases in my app, and I have a few follow up questions:

override fun onAttachedToWindow() {
    super.onAttachedToWindow()
     store.dispatch(REFRESH)
     subscription = store.subscribe {
        Anvil.render()
    }
}

That is, dispatching from onAttachedToWindow and thereby triggering the load?

Sorry for what may be naive questions - on the upside, I am happy to report that with Bansa I already implemented some stuff that I never really dared to in the current app, like a per-picture ProgressBar within a RecyclerView item (when up-voting an image or removing an up-vote).

Thanks again!