I am using React with react-router and Reflux as my datastore, but I am unsure on how to best deal with persistence to allow page refresh.
My components connect to the store with Reflux.connect, but since the store fetches the data from a backend, it is not available yet when the Components first initialize and render.
When the user enters my app from the start, then all this data is loaded in order and available when it needs to be, but if further down a route you trigger a page refresh, react tries to render components that rely on data that is not there yet.
I solved this by constantly keeping a copy of data in LocalStorage and serving that from the Reflux store getInitialState(), so that all components get the data before they render.
I wonder if this is the proper way to do it. When for some reason the local storage data gets cleared or corrupted, the interface goes blank, because the components cannot access the correct data. Substructures and properties don't exist and trigger javascript errors. It seems like a messy and unreliable solution.
I am curious to know what patterns are used to solve this.
------ edit -----
To answer to the comment of WiredPrairie:
1) Why are you initializing components with data in getInitialState?
When my components use Reflux.connect, they don't have the data in their state yet on the first render as the store still needs to fetch its data. My views currently don't work gracefully with undefined data. By returning the locally stored cache from the Reflux store in getInitialState(), all connected components will get that data before their first render call.
2) What's causing a page refresh and why can't the data be loaded in the same manner as it was the first time?
It's mainly a workaround I had to build around livereload refreshing the page when I make edits (will look into using react-hotloader later but is not an options yet), but users can also just hit refresh when they are somewhere in my nested views and that would have the same effect. When they manually refresh, they are not entering the app at the start.
3) When components are wired to the change events of a store, why don't they update then?
They do update, but like I said they don't deal with empty data right now and on first render they will miss it waiting for the store to fetch things. I can make all my views work gracefully with empty data, but that would add a lot of boilerplate code.
From the replies so far, I get the feeling that what I'm doing with localStorage is the common way to do it. Cache stuff locally in localStorage or sessionStorage or something similar, and serve that data immediately after a refresh.
I should make my views a bit more robust by gracefully handing empty data on the odd occasion that localStorage doesn't work properly.
I've been caching each Store in sessionStorage when its emitChange() fires, and initializing the store from sessionStorage if cached data exists, or null values otherwise. This seems to work provided that the views can handle null values, which is probably a good idea anyway (it sounds like this is your main problem).
I'd suggest making your views handle the case of no data gracefully, initialize everything with null values if the cache isn't available, and then call the backend to update the null values with an Action whenever the data returns.
I haven't tried Reflux, but in regular Flux it would look like this (maybe you can apply the same principle):
var _data;
if (sessionStorage.PostStore)
_data = JSON.parse(sessionStorage.PostStore);
else {
_data = {
posts: null
};
BackendAPI.getPosts(function(err, posts) {
if (posts) {
PostActions.setPosts(posts);
}
});
}
...
AppDispatcher.register(function(payload) {
var action = payload.action;
switch (action.actionType) {
...
case Constants.SET_POSTS:
_data.posts= action.data.posts;
break;
default:
return true
}
// Update cache because state changed
sessionStorage.PostStore = JSON.stringify(_data);
PostStore.emitChange();
return true;
});
Related
I have following code
const {
isLoading,
error,
data: calculatorProducts
} = useQuery('fetchSomething', ()=> fetchFunc())
This function should be called when I visit my main page, but when returning to this route from another page this request is not triggered and I don't get fresh data. What I want is this query to be called every time like useEffect
data from the cache is always returned if there is data in the cache. Additionally, react-query will update stale queries in the background on certain occasions, e.g. when a component mounts. This technique is called stale-while-revalidate.
All queries are instantly stale per default, so if you go to a route from a different page, you should get data from the cache and still see a request in the devtools. If not, then maybe your component isn’t really mounting?
If you want data to be removed from the cache when all components that use this data unmount, you can set cacheTime: 0. But it means you’ll get a hard loading with a spinner when you come back, which is what react-query is trying to avoid
As for now, initial data are cached so you also have to set:
initialData: undefined
This made my data fresh (it takes undefined as cached initial data, so each time it takes fresh data because cache is "empty")
This question might be considered to be "opinion-based", but I am just looking for inspiration here, so here it goes:
In my app I request data from a server. This data is not a list or anything, it is basically just a bunch of key-value-pairs, which should then be displayed to the user. A similar used case is described here Should I store search result in redux store?
Now the question ist: How do I store this data in the redux-store. Does it get its own reducer? Or do I store it in some kind of AppState-Store which itself is key-value-based?
I have searched through several blogs, but they only describe the typical use-case of a "ToDo-List", which does not apply in my case. At the end I would only have one Reducer-Case "SET_NEW_RESULTS_FROM_SERVER", which somewhat feels iffy.
You should model the entire request lifecycle:
REQUEST_RESULTS_FROM_SERVER
REQUEST_RESULTS_FROM_SERVER_SUCCESS
REQUEST_RESULTS_FROM_SERVER_FAILURE.
Why? Loading indicator, error messages displayed to the user, and other reasons.
The store is initialized with something like null (so your view can render before anything has been fetched) and when the response contains valid data, you save it there ("a bunch of key values pairs" sounds like a plain object to me).
Your data does not necessarily need its own reducer. Depends on your application of course, which we don't know.
Also, when you're fetching data via ajax in redux the reducer typically needs to keep track of:
The result, your payload in the response
Any error message in case of REQUEST_RESULTS_FROM_SERVER_FAILURE
Wether or not the request in still in progress - so your view can adapt accordingly
Example:
{
result: null,
error: null,
isLoading: false
}
I am building an application that requires a user to be a certain age before entering the site. The user is shown a form in which they must enter their date of birth, this information is then saved to local storage and then on every request check if that value is set in local storage. Currently inside main.js the checking code looks like:
let hasRememberedAge = localStorage.getItem('ageVerified')
if (hasRememberedAge !== null) {
store.dispatch(ageVerified())
}
This feels wrong, my question what is a better way to handle this? Should I fire an action on every page load which then triggers these checks? If so where should this dispatch live?
I don't see anything wrong with this. You can do this wherever you create the store.
Alternatively you can serialize a part of your state in a store.subscribe callback, and read it from the localStorage before creating the store. You can pass such persisted state as a second argument to createStore to preload it.
I am trying this solution to fetch the current user before my application is rendered to the user:
Em.Route.extend({
model: function() {
var currentUserPromise = this.store.find('user', 'me');
return Em.RSVP.all([
currentUserPromise
]);
}
});
So, I query the user with the ID me, which is a constant that my API recognizes as the currently authenticated user.
It works really well most of the way. The issue is that Ember-Data immediately creates a User model in the store with this ID constant me and no other attributes. This instance is not removed after the response has arrived and Ember-Data correctly stores the real User model.
I can't figure out where Ember-Data creates this temporary instance and if there is a way to prevent this behavior.
Either a solution to prevent Ember-Data from creating the temp. instance or remove it when the real data arrives will be fine.
Anyone with a solution?
After consulting with #emberjs I found that a much cleaner solution to this challenge is to request the active user by requesting a session object from my API.
By exposing a /sessions endpoint in my API I could expose the current session—which can also include auth token, etc.—with a relationship to the currently logged in profile. That resulted in a JSON payload like this:
{
"sessions": [
{
"id": 1,
"user": { ... },
"accessToken": "abcdef1234567890"
}
]
}
This means that I will no longer have the issue that Ember-Data caches a bogus User object with the id 'me', instead I will now have a session object where the active user is correctly loaded into the store and have it available through the session.
I think you are going about things the wrong way. You're in a single page application, which means the user can log out or log in at any time, and any rendering is essentially already happening before and after that. You can only control what you allow to render, but not the when.
Also, you are explicitly returning a set of promises which resolves when the single promise it contains is resolved. This achieves the same:
Em.Route.extend({
model: function() {
return this.store.find('user', 'me');
}
});
You don't know when that will resolve, but you're asking to "fetch the user before the app is rendered to the user". That's not how it works in Ember, as you can't really delay rendering. (Technically you can, but that offers terrible user experience giving the feeling the browser has locked up).
What you would want to do instead is to modify what you are rendering based on the state of the logged in user, and on the backend side you only ever send what that user is allowed to see. Anything else is insecure, as you can't ever trust the browser. It's pretty trivial to make an Ember app think you are logged in.
The issue is that Ember-Data immediately creates a User model in the store with this ID constant me and no other attributes.
I'm not sure about that one; I'm guessing ember-data creates a placeholder while it waits for the population of the real data (if ever). It is as if you're creating a new empty object in code.
I can't figure out where Ember-Data creates this temporary instance
Ember-data is in its core basically a cache (why it is called the store) with various adapters to read from and write to that cache and an API to use it. In your example me is created in the cache only, at least until you decide to save it.
Ember data modifies the same record instead of creating a new record after the materialization. There might be some other problem while finding the records for table. For example In ember data 1.0.0-beta.8 the the following code logs correct id rather than printing 'me'.
this.store.find('user', 'me').then(function(user) {
console.log(user.get('id'));
});
I've got a phonegap-wrapped sencha touch (v1.1.1) app that has a few stores, and a list.
The problem occurs after you're logged in as "User1", and then log out and log back in as "User2". The list won't refresh with the new account data fetched by the store proxy.
I'm currently attempting to call .refresh() on the List itself, which according to the docs will also cause the store to refresh its data (although, I'm manually doing that before hand anyway)
var sL = Ext.getCmp('AccountsList');
sL.refresh();
sL.scroller.scrollTo({x:0,y:0});
We've also tried to .sync() the stores with no results.
Ext.getStore('AccountsTransfersTo').sync();
Ext.getStore('AccountsTransfersFrom').sync();
Ext.getStore('AccountsStore').sync();
Any idea what the problem is, or has anyone run into something similar?
refresh won't reload the store, it just grabs whatever is in the store and re-renders the view.
sync is for updating the store when you've got local changes and you're posting them to the server.
You need to call the load method on the store.
It sounds like a caching problem. or a state problem.
a link:
http://html5wood.com/sencha-touch-difference-between-ext-get-and-ext-getcmp/
from the page:
Sencha Touch: Difference between Ext.get() and Ext.getCmp()
Important: It returns the same element object due to simple caching on retrieving again. It means it returns wrong element on second time. Ext.fly() can be used for avoiding this problem.
hope it helps
mike.
You may Load the store each time
var sL = Ext.getStore('AccountsTransfersTo')
sL.load();
sL.scroller.scrollTo({x:0,y:0});
or you may try to clear data form store and reload it again
var sL = Ext.getStore('AccountsTransfersTo')
sL.removeAll();
sL.load();
sL.scroller.scrollTo({x:0,y:0});