6

I use the match method for my server side rendering and the parameters in the callback are always undefined. Probably got something wrong but it's been a complete day and I can't get my head around it.

Here's my server side rendering.

// Create location from the history module.
let location = createLocation(req.url);

match({Routes, location}, (error, redirectLocation, renderProps) => {

    // TODO : Verify why this is always undefined
    console.log('ERROR :: ', error)
    console.log('REDIRECT LOCATION :: ', redirectLocation)
    console.log('RENDER PROPS :: ', renderProps)

    if (redirectLocation) {
        res.redirect(redirectLocation.pathname + redirectLocation.search)
    }
    // TODO : Verify why this is breaking
    //else if (error || !renderProps) {
    //  return console.log('Error while starting server :: ', error)
    //}
    else {

        Transmit.renderToString(RoutingContext, renderProps).then(({reactString, reactData}) => {

            console.log('REACT STRING :: ', reactString);
            console.log('REACT DATA :: ', reactData);

            let output = (
                `<!doctype html>
                <html lang="en-us" style="min-height:100vh; width: 100%; background-color: #eee;">
                    <head>
                        <meta charset="utf-8">
                        <title>react-isomorphic-pandora-app</title>
                        <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
                        <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:400,100,700">
                    </head>
                    <body>
                        <div id="react-root">${reactString}</div>
                    </body>
                </html>`
            );

            var webserver = process.env.NODE_ENV === "production" ? "" : "//localhost:8080"

            output = Transmit.injectIntoMarkup(output, reactData, [`${webserver}/dist/client.js`])

            res.send(output)

        }).catch((error) => {

            res.send(error.stack).type("text/plain").code(500)

        })
    }
})

Here is my client side.

import React from "react";
import ReactDOM from "react-dom";
import {Router} from "react-router";
import Transmit from "react-transmit";
import routes from "../views/Routes";
import {createHistory} from "history";

let reactRoot = window.document.getElementById("react-root");
let history = createHistory();
let location = history.createLocation();

const routerOption = {
    routes: routes,
    history: history,
    location: location
}

Transmit.render(Router, routerOption, reactRoot);

if (process.env.NODE_ENV !== "production") {
    if (!reactRoot.firstChild || !reactRoot.firstChild.attributes ||
        !reactRoot.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.");
    }
}

Here are my routes.

import React from "react"
import {Router, Route} from "react-router"
import MenuView from "./Menu"
import DefaultView from "./Default"
import AnotherView from "./Another"

export default (
    <Router component={MenuView}>
        <Route path="/" component={DefaultView} />
        <Route path="/another-view" component={AnotherView} />
    </Router>
);

Any help would be appreciated. Thanks :)

EDIT --------

Here's the code inside my MenuView component.

class MenuView extends React.Component {

    constructor(){

        super()

        this.menuItems = [
            {
                type: MenuItem.Types.SUBHEADER,
                text: 'Menu sub header'
            },
            {
                route: '/',
                text: 'Home'
            },
            {
                route: '/another-view',
                text: 'Another view'
            }
        ]
    }

    childContextTypes = {
        muiTheme: React.PropTypes.object
    };

    getChildContext() {
        return {
            muiTheme: ThemeManager.getMuiTheme(DefaultTheme)
        }
    }

    _getSelectedIndex = () => {

        let currentItem;

        for (let i = this.menuItems.length - 1; i >= 0; i--) {
            currentItem = this.menuItems[i];
            if (currentItem.route && this.props.history.isActive(currentItem.route)) return i;
        }

    }

    _onLeftNavChange = (e, key, payload) => {

        this.props.history.pushState(null, payload.route);

    }

    render () {

        var style = {
            paddingTop: '92px'
        }

        return (
            <div>
                <LeftNav
                    ref="leftNav"
                    menuItems={this.menuItems}
                    onChange={this._onLeftNavChange}
                    selectedIndex={this._getSelectedIndex()}
                    style={style}/>
                <section className="content">
                    {this.props.children}
                </section>
            </div>
        )
    }

}
Raphael Parent
  • 474
  • 5
  • 9

5 Answers5

4

I guess you use babel 6, NOT babel 5.

I had a similar problem. It's OK with babel 5, but NOT with babel 6.

After long debugging, I found that making the following change fixes the problem for me.

change:

 match({Routes, location}

to:

match({Routes.default, location}
YakovL
  • 7,557
  • 12
  • 62
  • 102
Kaman Wu
  • 189
  • 10
4

The problem is the exports default in the routes file. It returns an object like { defaults: { // routes in here } whereas on the client, it returns an object like { // routes in here }. You need to export the file in a different manner like modules.exports = [...routes...];.

Giorgio
  • 13,129
  • 12
  • 48
  • 75
delovely86
  • 53
  • 3
4

Due to Babel 5 vs. Babel 6 and exporting routes file it should look like this:

Babel 5:

  Router.match({ routes:routes, location: req.url}, etc ...

Babel 6:

  Router.match({ routes:routes.default, location: req.url}, etc ...
Jan
  • 378
  • 2
  • 6
3

You got es6 destructuring assignment wrong. Destructuring assignment

match({Routes, location}) needs to be match({routes, location})

Or more safe way

match({routes:Routes, location:location})

Nir Levy
  • 12,750
  • 3
  • 21
  • 38
user3763020
  • 53
  • 1
  • 4
0

Your routes are the issue. You have a top-level <Router> that you're passing into the RoutingContext after you match. That's backwards, as normally on the client side we create a RoutingContext within the <Router> component.

I think you just want to make the <Router> just a <Route> to provide your top-level <MenuView> component across all routes.

Tim Dorr
  • 4,861
  • 2
  • 21
  • 24