Search code examples
expressreactjsreact-engine

Server-side variables to Client-Side with React-Engine and Express


I'm reasonably new to React/React-Engine. I have a config on the server-side that I need to pass certain values of to client-side however I have a dependency on NODE_ENV in order to get the correct config out.

var config = {
    local: { ... }
    production: { ...}
}

module.exports = config[process.env.NODE_ENV]

Which works just fine server-side, however since I need to reference some of values contained in these objects on the client-side so I can't require(./config); in my React JSX.

Is there any easy way to pass this stuff into React? At the end of the day, I'd be happy if I could just pass the 'config' straight into React somehow without even having to worry about NODE_ENV on the client-side.

Thanks


Solution

  • The most common way to pass data from the server to client before rendering is to embed it in a global JavaScript variable on the page where your React is rendering.

    So, for example, in the middleware where you're actually rendering some template which includes your <script> tags with your React app, you could add the info and grab it on the template:

    var config = require('../config-from-somewhere');
    app.get('/', function (req, res) {
      res.render('index', {config: JSON.stringify(config)});
    });
    

    And an example mustache template:

    <html>
    <body>
    <script>
      window.CONFIG = JSON.parse({{{config}}});
    </script>
    <script src="my-react-app.js"/> 
    </body>
    </html>
    

    HOWEVER apparently react-engine already provides its own way to send data do the client-side:

    Data for component rendering

    The actual data that gets fed into the component for rendering is the renderOptions object that express generates.

    https://github.com/paypal/react-engine#data-for-component-rendering

    As you can see in this example, the movies json is simply being passed into render:

    app.get('*', function(req, res) {
      res.render(req.url, {
        movies: require('./movies.json')
      });
    });
    

    And then, by the grace of the framework's magic, probably on this line, the information is provided for your components and then the List uses it from props.movies.

    module.exports = React.createClass({
      displayName: 'List',
    
      render: function render() {
        return (
          <div id='list'>
            <h1>Movies</h1>
            <h6>Click on a movie to see the details</h6>
            <ul>
              {this.props.movies.map(function(movie) {
                return (
                  <li key={movie.id}>
                    <Router.Link to={'/movie/' + movie.id}>
                      <img src={movie.image} alt={movie.title} />
                    </Router.Link>
                  </li>
                );
              })}
    
            </ul>
          </div>
        );
      }
    });
    

    So, basically add your config to your render call and it should be available in your component's props.

    And for the very curious:

    Indeed, as we can see on this line onwards, the engine merges renderOptions and res.locals and finally passes that down to React.

    // create the data object that will be fed into the React render method.
    // Data is a mash of the express' `render options` and `res.locals`
    // and meta info about `react-engine`
    var data = merge({
      __meta: {
        // get just the relative path for view file name
        view: null,
        markupId: Config.client.markupId
      }
    }, omit(options, createOptions.renderOptionsKeysToFilter));
    

    And:

    return React.createElement(Component, merge({}, data, routerProps));