Reject previous route's pending action upon page transition in Redux app - react-router

I have Redux app with React Router (based on https://github.com/este/este).
Inside one Route, there may be more than 1 AJAX calls (fired by redux-promise-middleware & redux-thunk). When the page changes (via react-router) I wish to reject all remaining _SUCESS or _FAILED callback actions fired by the previous route.
What is the best way to do this?

I'd suggest that you make the data you fetch page-aware. Meaning that in the action where the fetch is started, add a page-context. When the reducer gets the data it can either save it for that page-context or it can throw it away if the location is not the same as your browser (meaning that the user has navigated away). If you keep the data for the different pages/contexts you also have the bonus of these being ready if the user returns (if that is something that you'd want).
You are on url "/pageX". You start fetching data and the action makes sure that the page-context is remembered for when the SUCCESS action is to be dispatched. When the reducer handles the action it stores the data in store.context["/pageX"].data (or similar). Note: This is where you could also throw it away (reject) in case the current location is not the same as the received data.
The UI should know how to ask/use data from the context that matches it's location only.
You might also want to consider tracking the browser-location in the state for the app...

Related

How to remember user choices in react router?

I am using react router to build a single page app.Now, everything is fine, except remembering the previous status. I mean: each time when user chooses one option in a select dom, then click a link component to jump to another route path; then, if this user clicks back button from browser to go back to the previous page, he can not see his previous choice, everything lost!
The desired behavior you are talking about is about saving the State of the Component. Even though the router renders the desired Component, it does not save the State of that Component.
However, you can pass in a desired State via URL Parameters, but that doesn't like a solution for you due to what exactly you are trying to achieve.
Passing data in through the URL to achieve the state may sound like a good idea, but this won't work due to the URL having to be updated as the User selects different options, and this will trigger new rendering. This will happen because you are currently on that route.
//the route path can be defined like this:
localhost:123/#/userselection/:selectedid
When you navigate it will look something like this
localhost:123/#/userselection/1
localhost:123/#/userselection/2
localhost:123/#/userselection/3
etc.
//Now if you are on
localhost:123/#/userselection/2
and the User changes to selectionid 3, you will have to trigger a render
which is not usually desired behaviour
An idea may be to have some sort of ComponentStateService that is declared at a root level of your application. Use this Service to set and read desired state. So when the User selects something, have an Object with the Component Name, and its stateData.
export default class ComponentStateService{
_stateData = {};
constructor(defaultState = null){
if (defaultState){
this._stateData = defaultState;
}
}
getComponentStateData(componentName){
if (this._stateData){
return this._stateData[componentName];
}
}
setComponentStateData(componentName, stateData){
this._stateData[componentName] = stateData;
}
}
Then initialize it something like this at a high level:
import ComponentStateService from './componentStateService';
var componentState = new ComponentStateService(//default state passed in here);
export default componentState;
Then to use it, import it to each component
import componentState from '../index';
// call this to set the state
componentState.setComponentStateData("component1", {});
// and retrieve state
let savedState = componentState.getComponentStateData("component1");
Then you retrieve or update as necessary. You can make use of the LifeCycle Hooks that react has in order to read and set the data before the Comopnent is Rendered. Also you will need to ensure that you are correctly updating the data for the state upon User changing their selection.
As you can see though, the major flaw is you cannot share your link and expect someone else to see what you see, since it will persist in a Users Browser Session.
You can create a base class that imports this, so you don't have to import it every time.
You can also dump the entire state to local storage so it persists beyond a session, and load it up on app start

react-redux handling multiple requests

I'm using redux + react-redux + react-native
There are times (when typing on a text field for example) that multiple requests are sent to the server. How can I handle this case, so that only the last one is processed on the client?
I've read fetch doesn't have a promise reject, so I'm not sure if there is a way to differ promises, or a flow or middleware to handle this correctly in redux, like keeping track of all requests order.
I'm currently using redux-saga or redux-observable, they internally use RxJS and can cancel previous actions, like when you use takeLatest, only the last action will get completely executed.
You should have :
an action that changes the state of the value of the textfield
an other action that makes the request on submit (or using debounce on the onChange)
Here is an example: https://topheman.github.io/react-es6-redux/ (try the dev version, you'll have access to redux-devtools & sourcemaps)
Usefull resource if you don't know about debounce : https://davidwalsh.name/javascript-debounce-function

How can I configure Polymer's platinum-sw-* to NOT cache one URL path?

How can I configure Polymer's platinum-sw-cache or platinum-sw-fetch to cache all URL paths except for /_api, which is the URL for Hoodie's API? I've configured a platinum-sw-fetch element to handle the /_api path, then platinum-sw-cache to handle the rest of the paths, as follows:
<platinum-sw-register auto-register
clients-claim
skip-waiting
on-service-worker-installed="displayInstalledToast">
<platinum-sw-import-script href="custom-fetch-handler.js"></platinum-sw-import-script>
<platinum-sw-fetch handler="HoodieAPIFetchHandler"
path="/_api(.*)"></platinum-sw-fetch>
<platinum-sw-cache default-cache-strategy="networkFirst"
precache-file="precache.json"/>
</platinum-sw-cache>
</platinum-sw-register>
custom-fetch-handler.js contains the following. Its intent is simply to return the results of the request the way the browser would if the service worker was not handling the request.
var HoodieAPIFetchHandler = function(request, values, options){
return fetch(request);
}
What doesn't seem to be working correctly is that after user 1 has signed in, then signed out, then user 2 signs in, then in Chrome Dev Tools' Network tab I can see that Hoodie regularly continues to make requests to BOTH users' API endpoints like the following:
http://localhost:3000/_api/?hoodieId=uw9rl3p
http://localhost:3000/_api/?hoodieId=noaothq
Instead, it should be making requests to only ONE of these API endpoints. In the Network tab, each of these URLs appears twice in a row, and in the "Size" column the first request says "(from ServiceWorker)," and the second request states the response size in bytes, in case that's relevant.
The other problem which seems related is that when I sign in as user 2 and submit a form, the app writes to user 1's database on the server side. This makes me think the problem is due to the app not being able to bypass the cache for the /_api route.
Should I not have used both platinum-sw-cache and platinum-sw-fetch within one platinum-sw-register element, since the docs state they are alternatives to each other?
In general, what you're doing should work, and it's a legitimate approach to take.
If there's an HTTP request made that matches a path defined in <platinum-sw-fetch>, then that custom handler will be used, and the default handler (in this case, the networkFirst implementation) won't run. The HTTP request can only be responded to once, so there's no chance of multiple handlers taking effect.
I ran some local samples and confirmed that my <platinum-sw-fetch> handler was properly intercepting requests. When debugging this locally, it's useful to either add in a console.log() within your custom handler and check for those logs via the chrome://serviceworker-internals Inspect interface, or to use the same interface to set some breakpoints within your handler.
What you're seeing in the Network tab of the controlled page is expected—the service worker's network interactions are logged there, whether they come from your custom HoodieAPIFetchHandler or the default networkFirst handler. The network interactions from the perspective of the controlled page are also logged—they don't always correspond one-to-one with the service worker's activity, so logging both does come in handy at times.
So I would recommend looking deeper into the reason why your application is making multiple requests. It's always tricky thinking about caching personalized resources, and there are several ways that you can get into trouble if you end up caching resources that are personalized for a different user. Take a look at the line of code that's firing off the second /_api/ request and see if it's coming from an cached resource that needs to be cleared when your users log out. <platinum-sw> uses the sw-toolbox library under the hood, and you can make use of its uncache() method directly within your custom handler scripts to perform cache maintenance.

Passing back value from Browse-Child VM chain to caller

MVVMCross
Windows Store
Android
I have a VM that browses a hierarchy (BrowseVm) and supports forward navigation via
ShowViewModel<LeafDetailVM>
to a leaf detail ViewModel (LeafDetailVM).
When the user is on the LeafDetail View they should be able to say "I want this one" and they will be returned to the View that initiated BrowseVm.
I cannot simply Forward Navigate to the Initiator because that would leave me with an invalid back stack. I cannot have the BrowseVM view as a NoHistory page as I need it be in the back stack to support going back from the LeafDetail view.
My plan is to have the initiator start BrowseVm with a GUID. BrowseVm will pass that GUID onto LeafDetailVM.
In the "I want this one" command I will raise a Message, containing the GUID, that both BrowseVM and the initiator are subscribed to. Then I will close LeafDetailVM.
When BrowseVM receives the notification of the message it will compare the GUID and if it matches it will close itself.
When the initiator receives the notification it will deal with the now chosen data.
Does this make sense? Have I missed a much simpler way of doing this?
This is similar to How to pass a parameter from a viewmodel to its parent viewmodel but that does not deal with the back stack.
Thanks
I suggest you try stop mentally coupling how the views work on a platform to the view-models.
With the custom presenter mechanism in MvvmCross, in the app (platform specific code) you can handle navigation to a certain view-model in different ways, including closing views, modify backstack, etc.
You can interpret navigation to a view-model in whatever way you want \ need.
You can for example pass from view-model some parameters in the ShowViewModel request which the view-presenter (IMvxViewPresenter) can interpret in different ways in the Show() to display a view-model.
In your case, you can actually navigate to initiator VM passing the selected info. In the view presenter, you can modify the backstack in the way you need.
On Android, make sure you read and know about all LaunchMode flags, for example LaunchMode.SingleTask which allows you bring the initiator activity to front without creating a new one.
It's not clear to me, is BrowseVm a parent view-model to the LeafDetailVM?
More info would be needed to understand exactly your scenario.

AngularJS form wizard save progress

I have a service in AngularJS that generates all the steps needed, the current state of each step (done, current, show, etc) and an associated directive that actually implements the service and displays the data of the service. But, there are 2 steps that are divided in 4 and 3 steps each:
Step one
Discounts
Activities
Duration
Payment Length
Step two
Identification
Personal data
Payment
How can I "save" the state of my form in case the person leaves the site and comes back later? Is it safe to use localStorage? I'm no providing support for IE6 or 7. I thought of using cookies, but that can end up being weak (or not)
Either local storage or cookies should be fine. I doubt this will be an issue, but keep in mind that both have a size limit. Also, it goes without saying that the form state will only be restored if the user returns on the same browser, and without having deleted cookies / local storage.
Another option could be to save the information server side. If the user is signed in, you can make periodic AJAX calls with the data and store the state on the server. When the user finishes all steps, you can make an AJAX call telling the server to delete any saved data it might have. This allows you to restore state even if the user returns on a different browser, as long as he is signed in.
Regardless of what direction you go with this, you can use jQuery's serialize method to serialize the form into a string and save it using your choice of storage.