Santiago VdeK
Santiago VdeK

Reputation: 72

Isomorphic-style-loader, Cannot read property 'apply' of null

Hi I've seen this same error and multiple possible solutions but none has been able to solve my issue (Probably because I'm lacking in depth understanding of the whole React structure).

I know that context.insertCss.apply(context, styles); isn't receiving the context and that's why the error is thrown, I've added the ContextProvider but I'm afraid this could be conflicting with my routing setup. Also used Daniel's answer to this question [Why does isomorphic-style-loader throw a TypeError: Cannot read property 'apply' of undefined when being used in unison with CSS-Modules

server index.js

    app.get('/*', (req, res) => {

  const matchingRoutes = matchRoutes(Routes, req.url);
  let promises = [];

  matchingRoutes.forEach(route => {
    if (route.loadData) {
      promises.push(route.loadData());
    }
  });

  // promise.then(data => {
  Promise.all(promises).then(dataArr => {
    // Let's add the data to the context
    // const context = { data };
    // const context = { dataArr };

    const css = new Set()
    const context = { insertCss: (...styles) => styles.forEach(style => css.add(style._getCss()))}

    const app = React.renderToString(
      <StaticRouter location={req.url}>
        <ContextProvider context={context}>
          <App/>  
        </ContextProvider>
      </StaticRouter>
    )

    const indexFile = path.resolve('./build/index.html');
    fs.readFile(indexFile, 'utf8', (err, indexData) => {
      if (err) {
        console.error('Something went wrong:', err);
        return res.status(500).send('Oops, better luck next time!');
      }

      if (context.status === 404) {
        res.status(404);
      }
      if (context.url) {
        return res.redirect(301, context.url);
      }

      return res.send(
        indexData
        .replace('<style id="myStyle"></style>',`<style type="text/css" id="myStyle">${[...css].join('')}</style>`)
          .replace('<div id="root"></div>', `<div id="root">${app}</div>`)
          .replace(
            '</body>',
            `<script>window.__ROUTE_DATA__ = ${serialize(dataArr)}</script></body>`
          )
      );
    });
  });
});

Added on the server the ContextProvider in the renderToString(..) method, also I'm replacing the html body so the received CSS is attached to the HTML response.

ContextProvider.js

import React from 'react';
import PropTypes from 'prop-types'
import App from './App'

class ContextProvider extends React.Component {
  static childContextTypes = {
    insertCss: PropTypes.func,
  }

  getChildContext() {
    return {
      ...this.props.context
    }
  }

  render() {
    return <App {
      ...this.props
    }
    />
  }
}

export default ContextProvider

Used the context provider from Daniel's answer (Reference above)

Client index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import ContextProvider from './ContextProvider';


const context = {
  insertCss: (...styles) => {
    const removeCss = styles.map(x => x._insertCss());
    return () => {
      removeCss.forEach(f => f());
    };
  },
}


ReactDOM.hydrate(
  <BrowserRouter>
    <ContextProvider context={context} />
  </BrowserRouter>,
  document.getElementById('root')
);

Passing the context through the ContextProvider as supposed.

App.js used inside the ContextProvider

import React from 'react';
import { renderRoutes } from 'react-router-config';
import { Switch, NavLink } from 'react-router-dom';

import Routes from './routes';

export default props => {
  return (
    <div>
      <ul>
        <li>
          <NavLink to="/">Home</NavLink>
        </li>
        <li>
          <NavLink to="/todos">Todos</NavLink>
        </li>
        <li>
          <NavLink to="/posts">Posts</NavLink>
        </li>
      </ul>

      <Switch>
        {renderRoutes(Routes)}
      </Switch>
    </div>
  );
};

Home.js where I'm trying to test the custom style

import React from 'react';
import withStyles from '../../node_modules/isomorphic-style-loader/withStyles'
import styles from '../scss/Home.scss';

function Home(props, context) {
  return (

      <h1>Hello, world!</h1>

  )
}

export default withStyles(styles)(Home);

routes.js describes the routes used.

import Home from './components/Home';
import Posts from './components/Posts';
import Todos from './components/Todos';
import NotFound from './components/NotFound';

import loadData from './helpers/loadData';

const Routes = [
  {
    path: '/',
    exact: true,
    component: Home
  },
  {
    path: '/posts',
    component: Posts,
    loadData: () => loadData('posts')
  },
  {
    path: '/todos',
    component: Todos,
    loadData: () => loadData('todos')
  },
  {
    component: NotFound
  }
];

export default Routes;

Almost sure there is an easy fix for this issue but it doesn't seem so trivial to me. Thank you in advance.

Upvotes: 1

Views: 1771

Answers (1)

digz6666
digz6666

Reputation: 1828

Please try to use the built in StyleContext of isomorphic-style-loader instead of custom context provider.

server.js:

import StyleContext from 'isomorphic-style-loader/StyleContext';

const insertCss = (...styles) => {
  const removeCss = styles.map(style => style._insertCss());
  return () => removeCss.forEach(dispose => dispose());
};

ReactDOM.render(
  <StyleContext.Provider value={{ insertCss }}>
    <Router>{renderRoutes(Routes)}</Router>
  </StyleContext.Provider>,
  document.getElementById('root')
);

client.js:

app.get('/*', function(req, res) {

  const context = {};

  const css = new Set(); // CSS for all rendered React components
  const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()));

  const component = ReactDOMServer.renderToString(
    <StyleContext.Provider value={{ insertCss }}>
      <StaticRouter location={req.url} context={context}>
        {renderRoutes(Routes)}
      </StaticRouter>
    </StyleContext.Provider>
  );

  if (context.url) {
    res.writeHead(301, { Location: context.url });
    res.end();
  } else {
    res.send(Html('React SSR', component));
  }
});

You can see example project here: https://github.com/digz6666/webpack-loader-test/tree/ssr-2

Upvotes: 1

Related Questions