Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I update redux store during a react-router transition?

I'm facing an issue on how to update store when a react-router transition occurs.

In my current implementation (below), update store before rendering next page. The issue comes up when the current page gets a store update based on the data for the next page: (1) the current page renders pointlessly (it's subscribed to store updates), because the updated store is for the next page (2) the current page breaks on render because updated store only has data for the next page.

superagent
  .get(opts.path)
  .set('Accept', 'application/json')
  .end((err, res) => {
    let pageData = res && res.body || {};
    store.dispatch(setPageStore(pageData));
    render(store);
  });

The reverse is problematic too, render next page before updating store. The issue now is the next page breaks on render because the data needed for the next page is not there until store is updated.

I'm either misusing the libraries, or my architecture is incomplete, or something else. help!

The rest of the sample code:

app

const React = require('react');
const Router = require('react-router');
const {createStore} = require('redux');
const {update} = React.addons;
const routes = require('./routes'); // all the routes
let store = {};
let initialLoad = true;

Router.run(routes, Router.HistoryLocation, (Handler, opts) => {
  if(initialLoad) {
    initialLoad = false;

    // hydrate
    const initialState = JSON.parse(document.getElementById('initial-state').text);
    store = createStore(appReducer, initialState);
    render(store);

  } else {
    superagent
      .get(opts.path)
      .set('Accept', 'application/json')
      .end((err, res) => {
        let pageData = res && res.body || {};
        store.dispatch(setPageStore(pageData));
        render(store);
      });
  }
});

function render(store) {
  React.render(
    <Provider store={store} children={() => <Handler/>} />, 
    document.getElementById('react')
  );
}

action & reducer

function appReducer(state = {}, action) {
  switch(action.type) {
    case 'SET_PAGE_STORE':
      return update(state, {$merge: action.pageData});

    default:
      return reduce(state, action);
  }
}

const reduce = combineReducers({
  // ..all the reducers
});

function setPageStore(pageData) {
  return {type: 'SET_PAGE_STORE', pageData};
}
like image 763
Tri Noensie Avatar asked Dec 23 '15 21:12

Tri Noensie


People also ask

Does Redux work with react router?

You can use the connected-react-router library (formerly known as react-router-redux ). Their Github Repo details the steps for the integration. Once the setup is complete, you can now access the router state directly within Redux as well as dispatch actions to modify the router state within Redux actions.

How is store connected to react Routes?

Connected React Router is a Redux binding for React Router v4 and v5. It synchronizes router state with Redux store via a unidirectional flow and uses react-hot-loader to facilitate hot reloading of functional components while preserving state.

Is react-router-Redux deprecated?

react-router-redux is deprecated. Use connected-react-router. by Gobinda Thakur | Medium. react-router-redux is deprecated.

How do you pass additional data while redirecting to a route in react?

Using Link So when we click on the Register link, it will redirect to the /register route. But Link also allows us to pass additional data while redirecting. Here, at the place of data_you_need_to_pass , we can pass a string or object , array and so on and in the /register route we will get that data in props.


2 Answers

You can use redux-thunk middleware to dispatch multiple actions one after another

See the awesome redux doc #Async Actions section for more!

So your fetch data action creator will look something like this:

function fetchSomeData(path) {
  return dispatch => {
    // first dispatch a action to start the spinner
    dispatch(fetchStarted(path))

    return superagent.get(path)
      .set('Accept', 'application/json')
      .end((err, res) => {
        if (err) {
          dispatch(fetchDataError(err)) // handle error
        } else {
          let pageData = res && res.body || {};
          dispatch(fetchSuccess(pageData)); // do whatever you want when fetch is done here
          // such as this action from redux-simple-router to change route
          dispatch(pushPath('/some/path'))
      });
  }
}

As you can see, by simply doing store.dispatch(fetchSomeData('somePath')), it will automatically first call fetchStarted to show spinner, and when the process is done, call fetchSuccess(path) to hide spinner, update state, rerender...etc, or call fetchError(err) to show an error message, and you can call actions to change routes anywhere in this process!

(you don't need redux-simple router if you don't like it, you can call history.pushState(null, '/some/path') to change route, I just found redux-simple-router really handy, because you don't need to pass the history everywhere, plus you have a UPDATE_PATH action you can listen to if you want to track route changes)


Furthermore, I recommend redux-simple-router when you use react-router with redux, it allows you to watch route changes with the UPDATE_PATH action type, and pushPath action to change route. Also, I notice you're using an out-dated version of react-router...

If you want to use the latest version of react-router with redux-simple-router (together with redux-thunk), check out this repo!

you can find its store configuration, router setup in these files:

src/main.js                 // calls reactDOM.render(<Root />, ...) to render <Root />
src/containers/Root.js      // <Root /> is a wrapper for react-redux <Provider />
src/redux/configureStore.js // store configuration, how redux-thunk middleware is configured
src/routes/index.js         // routes are defined here
like image 154
DaxChen Avatar answered Sep 29 '22 03:09

DaxChen


One solution would be to make your pages compatible with having incomplete data, until the new page data is fetched. Then you could push the new path, and it would render immediately as much as it could, and your UI components render spinners (or something equivalent) until the data for the next page is fetched and dispatched to the store. On the second pass, the page would be re-rendered fully.

The other thing that comes to mind is that ideally, the shape of your store is such that all your pages are fundamentally compatible with all possible states that it could be in, without conflict. That will let you do things like prefetching data, which could unlock ways to minimize transition time, and to cache previous views. After all, single-page architecture isn't especially useful when it still requires a round-trip.

like image 45
acjay Avatar answered Sep 29 '22 03:09

acjay