erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12.01k stars 2.5k forks source link

SOLVED: Hard time getting dynamic routes to work #1233

Open JoeGrasso opened 8 years ago

JoeGrasso commented 8 years ago

I've been on this for over a week with no success. Here is my status so far ...

exports.getRoutes =(store) => {
  let dynRoutes = [];
  store.dispatch(loadNav()).then(result => {
    dynRoutes = result;
  });
  return (
  /**
   * Please keep routes in alphabetical order
   */
    <Route path="/" component={App}>
      { /* Home (main) route */ }
      <IndexRoute component={Home}/>
      { /* Get Dyanamic routes from database */ }
      { dynRoutes.length > 0 ?
        <DynamicRoutes dynRoutes={dynRoutes } />
        : ''}
      { /* Catch all route */ }
      <Route path="*" component={NotFound} status={404} />
    </Route>
  )
}

While the callback does occur and dynRoutes does have a value, the return has already completed and the component DynamicRoutes is no longer called with the actual dynRoutes prop values.

Any ideas?

JoeGrasso commented 8 years ago

The short answer is I needed to make not only the dynamic-routes.shared.js asynchronous, but also any other function calls, from both the server & client.

I used promises on the dynamic-routes.shared.js, and async/await on the server/client calls to dynamic-routes.shared.js.

I didn't bother throwing the data into a JSON, you can pretty much figure that out yourself.

1. dynamic-routes.shared.js

      function routesWithStore(store) {
       return new Promise(function(resolve, reject) {
       // you can use something like this to actually have these
       // routes in a database
       // let dynRoutes= [];
       // store.dispatch(loadNav()).then(result => {
       // dynRoutes = result;
       // }) 
       // resolve(dynRoutes.map(route => {
       //   ..... your code here .....
       // }))
         resolve(
           {
             path: '',
             component: App,
             childRoutes: [
               {path: '/', component: Home},
               {path: 'home', component: Home},
               {path: 'about', component: About},
               {path: '*', component: NotFound}
             ]
           }
         )
       });
     }

       function getRoutes(store) {      
        return(
          routesWithStore(store)
         )
       }

     exports.getRoutes = getRoutes;

2.client/entry.js

    // async call to dynamic-routes.shared.js ////////
    async function main() {
      try {
        const result = await getRoutes(store);
        processRoutes(result);
      } catch(err) {
        console.log(err.message)
      }
    }

    function processRoutes(result) {
      const component = (
        <Router render={(props) =>
            <ReduxAsyncConnect {...props} helpers={{client}} 
              filter={item => !item.deferred} />
                } history={history}>
                {result} <------------- client value from dynamic-routes.shared.js
              </Router>
            );

      ReactDOM.render(
        <Provider store={store} key="provider">
          {component}
        </Provider>,
         document.querySelector('#root');
         );

_

  if (process.env.NODE_ENV !== 'production') {
        window.React = React; // enable debugger

      if (!dest || !dest.firstChild 
                || !dest.firstChild.attributes 
                || !dest.firstChild.attributes['data-react-checksum']) {
      console.error
       ('Server-side React render was discarded. ' + 
        'Make sure that your initial render does not contain any client-side code.');
       }
     }

      if (__DEVTOOLS__ && !window.devToolsExtension) {
       const DevTools = require('shared/redux/dev-tools/dev-tools.redux.shared');
       ReactDOM.render(
        <Provider store={store} key="provider">
           <div>
            {component}
            <DevTools />
           </div>
        </Provider>,
         document.querySelector('#root');
        );
      }
    }

   main();

3. isomorphic-routes.config.server

     module.exports = (app) => {
      app.use((req, res) => {
        if (__DEVELOPMENT__) {
          // Do not cache webpack stats: the script file would change since
          // hot module replacement is enabled in the development env
          webpackIsomorphicTools.refresh();
        }
        const client = new ApiClient(req);
        const memoryHistory = createHistory(req.originalUrl);
        const store = createStore(memoryHistory, client);
        const history = syncHistoryWithStore(memoryHistory, store);

        function hydrateOnClient() {
          res.send('<!doctype html>\n' +
            ReactDOM.renderToString(

              <Html assets={webpackIsomorphicTools.assets()} 
                   store={store}/>));
        }

        if (__DISABLE_SSR__) {
          hydrateOnClient();
          return;
        }

_

        // Async call to dynamic-routes.shared.js ////////

        async function main() {
          try {
            const routesResult = await getRoutes(store);
            // pass routesResult below
            match({history, routes: routesResult, location: req.originalUrl}, 
                 (error, redirectLocation, renderProps) => {
              if (redirectLocation) {
                res.redirect(redirectLocation.pathname + redirectLocation.search);
              } else if (error) {
                console.error('ROUTER ERROR:', pretty.render(error));
                res.status(500);
                hydrateOnClient();

_

              } else if (renderProps) {
                loadOnServer({...renderProps, store, helpers: {client}}).then(() => {
                  const component = (
                    <Provider store={store} key="provider">
                      <ReduxAsyncConnect {...renderProps} />
                    </Provider>
                  );

                  res.status(200);

                  global.navigator = {userAgent: req.headers['user-agent']};

                  res.send('<!doctype html>\n' +
                    ReactDOM.renderToString(
                         <Html assets={webpackIsomorphicTools.assets()}   
                          component={component} store={store}/>));
                });
              } else {
                res.status(404).send('Iso Routes Not Found ' + routeResult);
              }
            });

          } catch(error) {
            console.error(error);
          }
        }

        main();

      });
    };

I hope this helps anyone looking to make their isomorphic routes dynamic.
Make America great again!

jboonmee commented 7 years ago

Have you tried using require.ensure in your project? I opened issue #1250 . Any thoughts?