Isomorphic React/Redux App: avoid extra API fetch on server render

415 Views Asked by At

I have an isomorphic (aka universal) React app that I'm working on mostly to learn.

The backend is a separate project that both the server and client makes requests to. So it's not the same server that renders the React app.

Server rendering is set up like this, a bit simplified:

const
    data = {
        body: '',
        initialState: {}
    }, 
    store = configureStore(); // Middleware and so on

match({location, routes}, (err, redirectLocation, renderProps) => {
    // Handle error
    // Handle redirect

    fetchData(store.dispatch, renderProps.components, renderProps.params)
        .then(() => {
            data.body = renderToString({
               <Provider store={store}>
                   <RouterContext {...renderProps} />
               </Provider> 
            });

            data.initialState = store.getState(); // dehydrate

            return renderToStaticMarkup(<HtmlComponent {...data} />);
        })
        .then((html) => res.status(200).send('<!doctype html>' + html))
        .catch((err) => res.status(500).send(err.stack));
});

The method fetchData() looks like this:

export function fetchData(dispatch, components, params) {
    const needs = components.reduce((prev, current) => {
        if(!current) return [];

        return Object.keys(current).reduce((acc, key) => {

            return current[key].hasOwnProperty('needs') ? current[key].needs.concat(acc) : acc;
        }, prev);
    }, []),

    promises = needs.map((need) => dispatch(need(params)));

    return Promise.all(promises);
}

A connected React component is set up like this:

const Component = React.createClass({
    statics: {
        needs: [
            Actions.getHistory
        ]
    },

    componentWillMount: function() {
        const needs = this.constructor.needs;

        for(let i = 0, len = needs.length; i < len; i++) {
            this.props.dispatch(needs[i]());
        }
    },

    // Code
};

On the server the needs actions for a route is completed using fetchData() before the react app is executed. In the client I want the needs actions to be dispatched on componentWillMount(), but only if it's not the initial route. On the initial route the actions are already completed on the server and the store rehydrated in the client.

The problem is that I don't know how to avoid dispatching actions in componentWillMount() on the server and on the initial route in the client. Which means that when the page is server rendered the actions are dispatched thrice.

I've set up the project to learn how to write isomorphic apps. This setup is something I think is pretty common and I've seen used a lot around the internet. But I've not found a way to prevent the double/triple action dispatches.

The only thing I can come up with is a global state like isServerRender = true/false and isRehydratedStateFresh = true/false. But I would really like to avoid global states.

Is there a conventional way to solve this? Or a solution that doesn't require global states?

0

There are 0 best solutions below