bionia
bionia

Reputation: 41

Invariant Violation: Element type is invalid: expected a string or a class but got: undefined. Check the render method of `MyApp`

I'm trying to load a static HTML from the meteor server side that also uses redux and router. However, every time I try to render my component with renderToString() I get the error indicated in the title. What am I doing wrong?

I've already tried using React.renderComponentToString as a possible alternative, but I get the error that React.renderComponentToString is not a function. How else am I going to pass an argument?

import React from 'react';  
import {onPageLoad} from 'meteor/server-render';  
import {StaticRouter} from 'react-router';  
import {Provider} from 'react-redux';  
import {createStore, applyMiddleware} from 'redux';  
import {Helmet} from 'react-helmet';  
import rootReducer, {initialState} from '../redux/reducers';
import HomePage from '../ui/homepage/HomePage';

export default renderServerPage = onPageLoad(sink => {

  const context = {};
  const store = createStore(rootReducer, initialState, applyMiddleware(thunk));

  const MyApp = props => {
    return (
      <Provider store={store}>
        <StaticRouter location={sink.request.url} context={context}>
          <Route path="/" component={HomePage}/>
        </StaticRouter>
      </Provider>
    );
  }

  // The following line is causing the error
  sink.renderIntoElementById('app', renderToString(<MyApp />));

  const helmet = Helmet.renderStatic();

  sink.appendToHead(helmet.title.toString(
    "Afterschools, Enrichment Programs, Growth, & Experiences - Fun2Bright"
  ));

  sink.appendToBody(
    <script>
      window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
    </script>
  );
});

My other attempts include the following: 1) creating a component outside of the scope within the same file and passing store and sink.request.url as props

function MyApp (props) {
  const context = {};
  return (
    <Provider store={props.store}>
      <StaticRouter location={props.location} context={context}>
        <Route path="/" component={HomePage}/>
      </StaticRouter>
    </Provider>
  );
}

2) basically creating the same component but in another file and importing the component.

3) directly putting the element inside the renderToString:

sink.renderIntoElementById('app', renderToString(
    <Provider store={store}>
      <StaticRouter location={sink.request.url} context={context}>
        <Route path="/" component={HomePage}/>
      </StaticRouter>
    </Provider>
));

4) using React.createElement without passing any props:

sink.renderIntoElementById('app', renderToString(React.createElement(MyApp)));

5) commenting out the following just to see if it's caused by any of my other imported components

<Route path="/" component={HomePage}/>

Upvotes: 1

Views: 80

Answers (1)

bionia
bionia

Reputation: 41

I finally figured the issue. It turns out that simply using react-router doesn't allow me to use DOM-aware components like and , so I needed to use react-router-dom instead. react-router-dom also re-exports all of react-router's exports, so I'll only ever need to use react-router-dom. I changed everything to be imported from 'react-router-dom':

import {Route, StaticRouter} from 'react-router-dom';
...
// inside onPageLoad
const App = props => {
    return (
      <Provider store={store}>
        <StaticRouter location={props.location} context={context}>
          <Route path="/" component={HomePage}/>
        </StaticRouter>
      </Provider>
    );
  }

sink.renderIntoElementById('app', renderToString(<App store={store} location={sink.request.url}/>));

I found this from here: https://github.com/ReactTraining/react-router/issues/4648

Upvotes: 1

Related Questions