faceyspacey/redux-first-router-demo

Question: Persisting data example / best practice.

Closed this issue · 12 comments

Love what you are doing! react-first-router makes so much sense!

Question about how you see making API requests to save data in relation to this project. For example, if I have a USER_PROFILE: { path: '/profile', ... } route, I can fetch the data for the existing profile via the route's thunk per your examples if I don't already have it. But when the user makes a change and clicks 'SAVE'... now what? Do you feel this should be handled by a route
( something like: SAVE_PROFILE: { path: '/profile/save', ... } ) or would we fall back to using "traditional" thunk based actions. I can see advantages / disadvantages to both, so I was curious about your (and other's) opinions on the best way to handle pushing data to the server rather than fetching it.

Glad to hear it.

U know what, I'm starting to consider having non route "routes" in the map lol, based on how popular it's been. Nobody wants to go back to the old way, myself included.

So basically we could pair "follow-up" thunks to "setup" action types without changing the URL, i.e. without a path option.

We are gonna do this!

Also u could have a URL like /saving that only shows for a second or two while the post is performed.

I actually think that's a fine option. Now that the URL is so easy why not change it temporarily like gmail flashs the document.title (ie the browser tab title) when u have a pending chat message.

Yeah, one of my thoughts (the 2nd option in my firs post) was that transient route that exists basically for dispatching thunks then switch back to the initial URL. The possible downsides is what happens if you visit that URL directly? What happens if you hit the back button after the save completes? If you are using server side rendering and request that page directly?

The argument for doing saves with "normal" thunk actions is that you aren't really navigating anywhere in a lot of cases (though when you are doing create type stuff, you may well be) so dispatching an "inline" action makes sense, but then you have fetching async stuff in one place (routes) and saving async stuff in another place. Not an awful fate, but having your data operations in one place certainly is nice to have.

Another, possibly terrible, idea is adding an optional flag or something to a route. So I could dispatch: {type: USER, payload: { id: 'veddermatic' }, flag: 'save' } and have my thunk do something different based on that. Although I don't see what that really gets me over transient routes other than consolidating the code into one place in the routes map.

In tradition server side apps u could post to them and u have to validate the data anyway. The case is still true today. You gotta do validation in ur thunks, again in the server, etc. So it shouldn't matter that u URL-ize the same action. I didn't quite get what u meant by flag: save.

As far as flags, the idea I have is simply if there is no path on the route object, it's a pathless route. I didn't get what you meant by flag: 'save'?

I guess the thought behind flags on a route are akin to HTTP verbs. So you can have the same route actions do different things:

{type: PROFILE}, // show the profile, fetch if needed
{type: PROFILE, flag: 'post'}, // persist the profile to the server
{type: PROFILE, flag: 'delete'}, // remove the profile

So to the view-handling reducer and display, it's all just PROFILE, but your route handler can do different things inside the thunk based on that flag.

Again, I don't think this is a good idea at this point, just an idea on how to handle non-fetch data while keeping like code near.

re: Path-less routes -- are they just traditional thunk actions by another name?

Almost. They come with the added benefit of not requiring the thunk to dispatch an initial action, and the structure of the routesMap.

Regarding the verbs thing, yea prolly not the best idea to introduce now. But there are possibilities for more dynamic routes. For example: if you use a regex in your path with parentheses we capture an unnamed variable at payload.0 and payload.1 etc. we could let you use $1 and $2 in the type key to create multiple action types per route.

It's also probably unnecessary complexity, but I have a feeling something will eventually emerge for individual routes with further dynamic capabilities.

i'm not sure what the point of this is and it seems very convoulted.

when data is going to be saved based on user interaction, simply call the action creator that was added to the components props by react-redux's connect and handle it any way you handled it before, using your preferred side-effect-handling redux middleware of choice (thunk, promise, saga, observable, loop etc).

why would you ever want a route for that? just because you can have a thunk in the routeMap? Unless you really want the URL to change while saving, this seems stupid.

The URL changing is stupid. Having the URL in the routesMap is important though--now you don't need to have 2 separate ways of dealing with what's pretty much the same thing. So that means more consistent organization.

That said, seeing the URL change for a second is just like another loading... spinner. So it's not unhelpful. In addition, analytics tools operate based on history changes--so it could lead to some nice automation there. I wouldn't completely discount it.

Why is that? I have to dispatch an action to save things anyway. I can either dispatch an action that goes through the route reducer and performs a side-effect in a thunk of a route that i have to specify for that case - or perform a side effect via redux middleware directly.

This is not the same thing. Previously, all sideeffects and async actions were located in redux middleware and executed in response to dispatched actions. Now, some additional side-effects might be tacked onto route definitions. That doesn't sound like something i'd want to make the default case.

"view redux middleware DIRECTLY" is in fact less direct. There's more work, as it doesnt do the setup action automatically, and there's no formalized pattern on organizing your thunks, just files filled with them. In my opinion, the whole middleware setup was never direct, just a loose hodge-podge of things.

With this, you just dispatch a sync action, and know exactly where to add a thunk when it becomes important (as well as other related things like pre-fetching which is coming up).

Lots of people want to be able to use non URL-ized actions in the routesMap by the way. It's been a request by several people. The formal contract and organization is very useful once your app gets filled with too many thunks that are hard to keep track of.

I agree to disagree. I don't think this is a good pattern. Sometimes it's better having no abstraction than the wrong one. It's definately nothing i want to see in a router, no matter how many people want it or how bad they do.

You already have one or more places where you group your actionCreators. Adding an async action is only a matter of adding sth. like this:

const persistData = data => dispatch =>
  api.persist(data)
    .then(persisted => dispatch(persistSuccess(persisted)))
    .catch(error => dispatch(persistError(error)))

What exactly is more direct combining this with a route?

All this tells me is that there is a need for better organizational patterns and more documentation, tutorials and screencasts on how to organize your side-effects with redux middleware.

It might even lead to new packages/modules that help doing so and can also be used in combination with redux-first-router routeMaps or instead of them.

Just don't mix things together that don't belong.

RFR now supports "pathless routes". Check the top of the main RFR repo for more information on this update.

Basically now your entire app can be made with actions + thunks specified in the routesMap. The overall goal is to prevent the typical explosion of the number of actions your app has, which becomes hard to remember, maintain, etc. The reason this occurs is subtle and somewhat psychological: essentially many apps end up with lots of "setter/getter" style actions. But when you start attach URLs/paths to your actions, it formalizes the contract and you end up dispatching less actions (especially unnecessary consecutive synchronous actions) and make your reducers fatter/smarter. This is what happens when properly using RFR before pathless routes existed. Now that they exist, they break that contract a bit, by having all your actions in one routesMap along side very formal path-based routes, you hopefully start developing an eye for keeping the # of actions your app uses at once. I've seen many developers new to redux do this:

dispatch(action1)
dispatch(action2)

I've seen far worse. 3 or 4 actions all dispatched synchronously one after the other. That's incorrect. What's correct is making your reducers all smarter by having them all correctly respond to just the first action. So in the RFR school of thinking, routes very much help developers move in that direction. They begin to recognize that if they want their app to respond properly to the user's usage of the browser back/next buttons, he/she is gonna have to make the reducers all interpret a single action. I mean they can do crappy things like dispatch more actions in route thunks or in component lifecycle handlers of the primary component that renders for each route, but they'll at least be pushed in the right direction to do this professionally. There's always exceptions of course, but the multi consecutive synchronous dispatch thing is generally an anti-pattern.

So yea, it's up to you if you're gonna use pathless routes. The real idea is making a *single *solid pattern accessible to junior developers. Keeping them living in the routesMap is a good thing. They likely already started off on a good foot with route-based paths--so by thinking of other async work in the same way is just good for their health.

Here's an example:
https://gist.github.com/faceyspacey/2771c05a62a338fa6d3d23d76e6e7c5a