Search code examples
javascriptreactjsreact-routerrefluxjs

React routing and persistence on page refresh


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.


Solution

  • 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;
    });