Server Side React Initial Render causes duplicated async calls
Asked Answered
O

2

6

I'm using react-router 2.0 on the server. Some of my top-level route components depend on async data to be fetched and uses Redux to manage state. To deal with this, I'm using a static fetchData() method to return a Promise.all of async actions as suggested in the official Redux docs. It works pretty well - the initial render from the server comes with the appropriate data in the Redux store.

My issue comes from how to write these async calls in a way that will work on both the initial render on the server and subsequent renders on the client as different routes get loaded. Currently, I make calls to the static fetchData() method in my top-level component's componentDidMount(), but this would cause the fetchData() to be called once the component mounts on the client. This is what I want when navigating to the route from another, but not on the initial render as we already made this call on the server.

I've been struggling with writing my data fetching logic in a way that:

  1. Only gets called once on initial render on the server, not on the initial client render
  2. Will render when a user navigates in from another route.

I was thinking of injecting a prop like { serverRendered: true } into the initial component but I only have access to the <RouterContext> when calling renderToString(). I took a peek at the source for but it doesn’t seem like I can pass the property from that JSX tag either.

I guess my questions would be:

  1. Is there a way to inject a property in <RouterContext> so that it passes down to the 'parent' top-level route component? If not in <RouterContext>, is there another way to inject the property in?
  2. If #1 isn't possible, is there a better way to handle fetchData() so the initial render won't cause fetchData() to be called on both the server and client?

Here’s a code sample of my server.jsx, the fetchData() handling middleware and an example top-level compnent.

Oligocene answered 11/2, 2016 at 14:3 Comment(2)
Having the same issue, did you come up with any solution ? I am also thinking to use a flag such as { serverRendered: true } on page load and then turn it off afterwards.Blackfellow
Hey @mahdipedram. I never really found a great solution to this problem. I'm still using the static fetchData() method to for the initial server-side load, and checking in componentDidMount() whether the Redux data exists before making another call for the required data. It feels sloppy, but it works. If you ever come across a better method, please post it here!Oligocene
I
1

I faced up with the same issue. Clearing some global value with setTimeout or checking Redux store for data existence didn't look good for me.

I decided to check on which paths initial data was loaded, put this array as global value and check matches for route on componentDidMount.

My server index.js

const pathsWithLoadedData= []; // here I store paths

const promises = matchRoutes(Routes, req.path)
    .map(({ route }) => {
        if (route.loadData) {
            // if route loads data and has path then save it
            route.path && pathsWithLoadedData.push(route.path);
            return route.loadData(store);
        } else {
            return Promise.resolve(null);
        }
    })

My render template

<body>
   ...
   <script> 
      window.INITIALLY_LOADED_PATHS = ${JSON.stringify(pathsWithLoadedData)};
   </script>
   ...   
</body>

Some componentDidMount function

componentDidMount () {
    if (!wasFetchedInitially(this.props.route.path)) {
        this.props.fetchUsers();
    }
}

And helper

export const wasFetchedInitially = componentPath => {
    const pathIndex = window.INITIALLY_LOADED_PATHS.indexOf(componentPath);
    if (pathIndex < 0) return false;

    window.INITIALLY_LOADED_PATHS.splice(pathIndex, 1);
    return true;
};
Indefatigable answered 1/4, 2020 at 14:22 Comment(1)
Thanks, this solved my issue. Also helped in bumping my Lighthouse scores from a paltry 24 to a respectable 70Hecht
B
0

I inject a global variable window.__INITAL_FROM_SERVER__ = true and clear it with setTimeout from client side.

Buskined answered 7/9, 2018 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.