Component does not receive props (React SSR)

881 Views Asked by At

I have a fork of Redux saga boilerplate and I try to update store on the server side by action. It goes well, but the component does not update (don't call mapStateToProps) when the store was updated. What's wrong? Help, please.

Server log

Component source:

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { testAction } from '../../actions';

class Event extends Component {
  static propTypes = {
    title: PropTypes.string,
    testAction: PropTypes.func
  }
  componentWillMount() {
    this.props.testAction({ test: 'test' });
  }
  render() {
    return (
      <div>
        Event - {this.props.title}
      </div>
    );
  }
}

function mapStateToProps(state) {
  console.log(state);
  return {
    title: state.default.test
  };
}

export default connect(
  mapStateToProps,
  { testAction }
)(Event);

server.js source:

import Express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import favicon from 'serve-favicon';
import compression from 'compression';
import http from 'http';
import proxy from 'express-http-proxy';
import path from 'path';
import url from 'url';
import { match, createMemoryHistory } from 'react-router';

import config from './config';
import configureStore from './store/configureStore';
import Html from './helpers/Html';
import getRoutes from './routes';
import waitAll from './sagas/waitAll';
import { Root } from 'containers';

const app = new Express();
const server = new http.Server(app);

// disable `X-Powered-By` HTTP header
app.disable('x-powered-by');

app.use(compression());
app.use(favicon(path.join(__dirname, '..', 'static', 'favicon.ico')));
app.use(Express.static(path.join(__dirname, '..', 'static')));

// Proxy to API
app.use('/api', proxy(config.apiBaseUrl, {
  // eslint-disable-next-line
  forwardPath: (req, res) => url.parse(req.url).path
}));

app.use((req, res) => {
  if (__DEVELOPMENT__) {
    webpackIsomorphicTools.refresh();
  }

  const memoryHistory = createMemoryHistory();
  const store = configureStore();
  const allRoutes = getRoutes(store);
  const assets = webpackIsomorphicTools.assets();

  function hydrateOnClient() {
    const htmlComponent = <Html assets={assets} store={store} />;
    const renderedDomString = ReactDOMServer.renderToString(htmlComponent);
    res.send(`<!doctype html>\n ${renderedDomString}`);
  }

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

  match({ routes: allRoutes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
    } else if (error) {
      console.error('ROUTER ERROR:', error);
      res.status(500);
      hydrateOnClient();
    } else if (renderProps) {
      const preloaders = renderProps.components
      .filter((component) => component && component.preload)
      .map((component) => component.preload(renderProps.params, req))
      .reduce((result, preloader) => result.concat(preloader), []);

      const runTasks = store.runSaga(waitAll(preloaders));

      runTasks.done.then(() => {
        const rootComponent = (<Root
          store={store}
          routes={allRoutes}
          history={memoryHistory}
          renderProps={renderProps}
          type="server"
        />);
        const htmlComponent = <Html assets={assets} component={rootComponent} store={store} />;
        const renderedDomString = ReactDOMServer.renderToString(htmlComponent);

        global.navigator = { userAgent: req.headers['user-agent'] };
        res.status(200).send(`<!doctype html>\n ${renderedDomString}`);
      }).catch((e) => {
        console.log(e.stack);
      });

      store.close();
    } else {
      res.status(404).send('Not found');
    }
  });
});

if (config.port) {
  server.listen(config.port, (err) => {
    if (err) {
      console.error(err);
    }
    console.info('==>   Open http://%s:%s in a browser to view the app.', config.host, config.port);
  });
} else {
  console.error('==>     ERROR: No PORT environment variable has been specified');
}
1

There are 1 best solutions below

0
On

In general, if you use server side rendering, then it will be much simpler and more effective to collect initialstate in advance and just to transfer it on the client in the form of a line.

Standard diagram of operation approximately following: you have several promises functions which derive data from some sources. When the address from already operating page on clients is required, you just launch the saga on fetch, and express broadcasts the response from a source.

And in case of SSR you just beforehand cause the summarize promises for all operations, and you send it in the form of the serialized object. Example:

Promise.all([ getData1(), getData2() ]).then((initialState) => {
    const store = createStore(handlers, initialState);
    // .....
    const htmlComponent = <Html assets={assets} component={rootComponent} store={store} />;
    const renderedDomString = ReactDOMServer.renderToString(htmlComponent);
})

Notice: in future versions of React with arrival of an engine of Fiber promise asynchronous rendering that the ReactDOMServer.renderToString functions already the Promise will allow to be so and to execute arbitrary sequence of appeals to data sources in an asynchronous mode.