2

Exact Error:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for co
mposite components) but got: object.
    in Posts
    in Connect(Posts)
    in Route
    in Switch
    in div
    in App
    in Route
    in Switch
    in div
    in Router
    in StaticRouter
    in Provider

I am getting error with doing server side rendering with react router 4. The error doesn't really point to anything in particular, but I think this comes from how I use the routes and exporting the components. I will post relevant code to this problem.

For client code I will post one component container since I export all of them in the same way. I just want you to get a since on how this is done. The component itself works just fine so I will be posting the export code at the bottom.

Note: this is a full working demo project (https://github.com/tbaustin/demo-SSR-RR4) and I use this same technique. I am also using turbo which is a scaffold type of system for react/redux. You will see that the app.js looks a little different than a normal express application, but I ensure you it works the same.

SERVER:

app.js:

require('babel-core/register')({
  presets: ['env', 'react', 'stage-0', 'stage-1']
});

const pkg_json = require('./package.json');
const vertex = require('vertex360')({ site_id: pkg_json.app });
var renderer = require('./renderer.js');

// initialize app
const app = vertex.app();

// import routes
const index = require('./routes/index');
const api = require('./routes/api');

// hopefully will be used on every Route, this should handle SSR RR4
app.use(renderer);

// set routes
app.use('/', index);
app.use('/api', api); // sample API Routes

module.exports = app;

renderer.js:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import serialize from 'serialize-javascript';
import { Helmet } from 'react-helmet';
import { matchRoutes } from 'react-router-config';

import routes from './src/routes';
import createStore from './src/stores';

function handleRender(res, req) {
  const store = createStore.configure(null); // create Store in order to get data from redux

  const promises = matchRoutes(routes, req.path)
    .map(({ route }) => {
      // Matches the route and loads data if loadData function is there
      return route.loadData ? route.loadData(store) : null;
    })
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve); // lets all data load even if route fails
        });
      }
    });

  Promise.all(promises).then(() => {
    const context = {};
    if (context.url) {
      return res.redirect(301, context.url); // redirect for non auth users
    }

    if (context.notFound) {
      res.status(404); // set status to 404 for unknown route
    }

    const content = renderToString(
      <Provider store={store}>
        <StaticRouter location={req.path} context={context}>
          <div>{renderRoutes(routes)}</div>
        </StaticRouter>
      </Provider>
    );

    const initialState = serialize(store.getState());

    const helmet = Helmet.renderStatic();

    res.render('index', { content, initialState, helmet });
  });
}

module.exports = handleRender;

index.mustache:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    {{{ helmet.title }}}
    {{{ helmet.meta }}}
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <link href="https://fonts.googleapis.com/css?family=Pathway+Gothic+One:300,400,500,600,700|Lato:300,400,400italic,600,700|Raleway:300,400,500,600,700|Crete+Round:400italic" rel="stylesheet" type="text/css" />
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
    <link rel="stylesheet" href="/dist/css/style.min.css" type="text/css" />
</head>
<body>
    <div id="root">{{{ content }}}</div>


    <!-- Turbo library imports: jQuery, Turbo CDN, sample app.js -->
    <script>
        window.INITIAL_STATE = {{{ initialState }}}
    </script>
    <script type="text/javascript" src="/dist/js/vendor.min.js"></script>
    <script type="text/javascript" src="https://cdn.turbo360-dev.com/dist/turbo.min.js"></script>
    <script type="text/javascript" src="/dist/bundle/commons.js"></script>
    <script type="text/javascript" src="/dist/bundle/app.js"></script> <!-- React code bundle -->
</body>
</html>

CLIENT:

index.js:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';

import store from './stores';
import routes from './routes';

const initialState = window.INITIAL_STATE;

ReactDOM.hydrate(
  <Provider store={store.configure(initialState)}>
    <BrowserRouter>
      <div>{renderRoutes(routes)}</div>
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

Root App component:

import React from 'react';
import { renderRoutes } from 'react-router-config';

import Header from './partials/Header';
import actions from '../actions';

const App = ({ route }) => {
  return (
    <div>
      <Header />
      {renderRoutes(route.routes)}
    </div>
  );
};

export default {
  component: App,
  loadData: ({ dispatch }) => dispatch(actions.currentUser())
};

Posts.js:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import swal from 'sweetalert';
import { Link } from 'react-router-dom';

import { CreatePost } from '../view';
import { Account } from '../containers';
import actions from '../../actions';
import { DateUtils } from '../../utils';

class Posts extends Component {
  componentDidMount() {
    if (this.props.post.all == null) {
      this.props
        .fetchPosts({})
        .then(response => {
          return null;
        })
        .catch(err => {
          console.log(err);
        });
    }
    if (this.props.reply.all == null) {
      this.props
        .getReplies({})
        .then(() => {
          return null;
        })
        .catch(err => {
          console.log(err);
        });
    }
  }

  createPost(params) {
    const { currentUser } = this.props.user;
    if (currentUser == null) {
      swal({
        title: 'Oops...',
        text: 'Please Login or Register before posting',
        type: 'error'
      });
      return;
    }

    const updated = Object.assign({}, params, { profile: currentUser });

    this.props
      .createPost(updated)
      .then(data => {
        swal({
          title: 'Post Created',
          text: `Title: ${data.title}`,
          type: 'success'
        });
      })
      .catch(err => {
        console.log(err);
      });
  }

  render() {
    const posts = this.props.post.all;
    const { currentUser } = this.props.user;
    return (
      <div>
        <div className="row">
          <div className="col-sm-8">
            <div className="card-columns">
              {posts == null
                ? null
                : posts.map(post => {
                    return (
                      <div
                        key={post.id}
                        className="card text-white bg-dark mb-3"
                        style={{ maxWidth: '20rem' }}
                      >
                        <div className="card-header">
                          <Link to={`/post/${post.id}`}>
                            <img className="card-img-top" src={post.image} alt="Card image cap" />
                          </Link>
                        </div>
                        <div className="card-body text-white">
                          <h4 className="card-title" style={{ color: 'white' }}>
                            {post.title.length > 17 ? post.title.substr(0, 17) + '...' : post.title}
                          </h4>
                          <p className="card-text">
                            {post.text.length > 30 ? post.text.substr(0, 30) + '...' : post.text}
                          </p>
                          <span>
                            ~{' '}
                            <Link to={`/profile/${post.profile.id}`} style={{ color: 'white' }}>
                              <strong>{post.profile.username || 'Anonymous'}</strong>
                            </Link>
                          </span>
                        </div>
                        <div className="card-footer">
                          <small className="text-muted">
                            {DateUtils.relativeTime(post.timestamp)}
                          </small>
                        </div>
                      </div>
                    );
                  })}
            </div>
          </div>
          <div className="col-sm-4">
            <div className="row">
              <div className="col-sm-12">
                <Account />
              </div>
            </div>
            {currentUser == null ? null : (
              <div className="row" style={{ marginTop: '25px' }}>
                <div className="row">
                  <div className="col-sm-12">
                    <h3>Create a Post</h3>
                  </div>
                </div>
                <div className="row">
                  <div className="col-sm-12">
                    <CreatePost onCreate={this.createPost.bind(this)} />
                  </div>
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    );
  }
}

const stateToProps = state => {
  return {
    post: state.post,
    user: state.user,
    reply: state.reply
  };
};

const dispatchToProps = dispatch => {
  return {
    createPost: params => dispatch(actions.createPost(params)),
    fetchPosts: params => dispatch(actions.fetchPosts(params)),
    getReplies: params => dispatch(actions.getReplies(params))
  };
};

const loadData = store => {
  return store.dispatch(actions.fetchPosts());
};

export default {
  loadData: loadData,
  component: connect(stateToProps, dispatchToProps)(Posts)
};

CLIENT/SERVER ROUTES:

import React from 'react';

import { Post, Posts, Profile, NotFound } from './components/containers';
import App from './components/App';

export default [
  {
    ...App,
    routes: [
      {
        ...Posts,
        path: '/',
        exact: true
      },
      {
        ...Post,
        path: '/post/:id'
      },
      {
        ...Profile,
        path: '/profile/:id'
      },
      {
        ...NotFound
      }
    ]
  }
];
3
  • The error occurs in your Posts component. Please post the code for this component. Commented Nov 7, 2017 at 1:51
  • The export data for the Posts component is already there but I will go ahead and post the entire component if you think that will help in an edit. Commented Nov 7, 2017 at 2:06
  • One problem could be if you're nesting your route configs where component of one would actually point to another config like component: { component }, rather than the component function itself. Just check "Pause on caught exceptions" in chrome and inspect the data to see what object got passed Commented Nov 7, 2017 at 5:05

1 Answer 1

1
+50

My best guess would be that, within Posts.js, this code:

<div className="col-sm-12">
  <Account />
</div>

should be:

<div className="col-sm-12">
  <Account.component />
</div>

Or you could extract the component part at a higher level somewhere.

I'm basing this from the fact that Account is imported from containers, and other parts of your code expect exported containers to be of the form { component, loadData }.

If not, the other culprit could be CreatePost.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.