3

How Can i have Next.js Like Data Fetching (getInitialProps) with React Router and React Loadable using Razzle. I had data fetching without react-loadable but without code splitting app bundle is too big and it's taking too long to load the page for clients.

this code works, but I just don't understand what I did about a year ago (it's a little bit different from the previous example)

    const promises = routes
        .map((route) => {
            const match = matchPath(req.url, route)
            if (match && route.component) {
                let promise

                if (typeof route.component.preload === "function") {
                    promise = route.component.preload()
                } else {
                    promise = Promise.resolve(route.component)
                }

                return promise
                    .then((res) => {
                        return res && res.__esModule ? res.default : res
                    })
                    .then((component) => {
                        let promises = []

                        // STATIC INTI ACTION
                        promises.push(store.dispatch(getToken()))

                        if (component.initialAction) {
                            let results = component.initialAction({ match })

                            results = Array.isArray(results)
                                ? results
                                : [results]
                            results.forEach((result) => {
                                promises.push(store.dispatch(result))
                            })
                        }
                        return promises
                    })
            }

            return null
        })
        .filter((el) => el !== null)

    // page not found
    if (promises.length === 0) {
        renderTree(req, res, store)
    } else {
        Promise.all(promises.map((data) => data.then((moreData) => moreData)))
            .then((data) => {
                Promise.all(data[0]).then(() => renderTree(req, res, store))
            })

Server.js

const promises = []

routes.some((route) => {
    const match = matchPath(req.url, route);
    if (match) {
        // route.component is React Loadable Component so getInitialData is undefined
        if (route.component.getInitialData) {
            promises.push(route.component.getInitialData({ match, req, res }))
        }
        return true;
    }
    return false;
});

Promise.all(promises)
    .then(renderReact)
    .catch(handleError)

// and at the end i will call
Loadable.preloadAll()
  .then(sendResponseToUser)

routes:

[
    {
        path: "/",
        exact: true,
        component: Loadable({
            loader: () => import("@views/Home"),
            loading: Loading,
        }),
    },
    {
        path: "/shop",
        exact: true,
        component: Loadable({
            loader: () => import("@views/Shop"),
            loading: Loading,
        }),
    },
]

My Components are Like This:

class Home extends React.Component {
  // This works similarly to Next.js's `getInitialProps`
  static getInitialData({ match, req, res }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          text: `
This text is server rendered if and only if it's the initial render.
Go to another route.
          `,
          currentRoute: match.pathname,
        });
      }, 500);
    });
  }

  render() {
    return <p>this is just a test</p>
  }

}

React Loadable Component have preload() method that can load Component so i tried: route.component.preload() but it's not working.

I tried loadable-components and it has the same problem with that too but I can replace react-loadable with loadable-components(my preferred library is loadable-components cause it's ok with StrictMode).

actually, after.js solved this problem (it uses Razzle) if I could extract code splitting logic and use it in my app or have some working example of data-fetching and react-loadable together it would be awesome.

Nima Arefi
  • 61
  • 9

1 Answers1

1

This is how i did this.

Its working with both. loadable Components and normal components.

Inside matchRoutes(). check for component.preload.

If its true, then allow it to load component.preload().

This will return a promise And after promise resolves, we will get our component. Then check for loadData/getInitialProps/fetchData, whatever static method name we are using.

return res.default.loadData ? res.default.loadData(store) : null;

you can call server side fetch request .. i am calling it loadData()

Always remember to return the promise, because its a chain of promise.

Aslo inside your redux actions, always remember to return the dispatch().

app.get('*', (req, res) => {
  // store instance for every incoming request
  const store = createStore(req);

  

  // loadData from api... before rendering
  const promises = matchRoutes(Routes, req.path)
    .map((comp) => {
      console.log(comp, comp.route.component.loadData)
      if (comp.route.component.preload){
        console.log("LoadableComponent")
        comp.route.component.preload().then(res => {
          return res.default.loadData ? res.default.loadData(store) : null;
        })
      }
      return comp.route.component.loadData ? comp.route.component.loadData(store) : null;
    })
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve);
        });
      }
    });


  Promise.all(promises).then(() => {
    console.log(store.getState())
    const context = {};
    const content = renderer(req, store, context);

    if (context.url) {
      return res.redirect(301, context.url);
    }
    if (context.notFound) {
      res.status(404);
    }
    

    Loadable.preloadAll().then(() => {
      res.send(content);
    });
  });
});
Raj D
  • 485
  • 1
  • 5
  • 11
  • I'm currently trying this approach but the call to the preload method is returning undefined to the promise chain. Can you enlight me about what might be wrong? in the if statement `comp.route.component.preload` is defined and is indeed a function, but then I get the 'TypeError: Cannot read property 'then' of undefined' error – lcmp Oct 29 '21 at 09:35