faceyspacey/react-universal-component

TypeError: load is not a function

good-idea opened this issue · 6 comments

I'm having trouble with some universal components not loading when they are first requested. They need to fail once (showing the NotFound component), then be requested a second time to work.

TypeError: load is not a function
    at requireUniversalModule.js:93
    at new Promise (<anonymous>)
    at requireAsync (requireUniversalModule.js:69)
    at ProxyComponent.requireAsync (index.js:280)
    at ProxyComponent.requireAsync (react-hot-loader.development.js:693)
    at ProxyComponent.componentWillReceiveProps (index.js:244)
    at callComponentWillReceiveProps (react-dom.development.js:12564)
    at updateClassInstance (react-dom.development.js:12774)
    at updateClassComponent (react-dom.development.js:14262)
    at beginWork (react-dom.development.js:15082)

I'm using the same <UniversalComponent /> that is in the universal-demo repo. My main App looks like this:

const App = () => (
	<Outer>
		<Router>
			<Homepage path="/" />
			<UniversalComponent path="/page-a" page="PageA" minDelay={1200}  />
			<UniversalComponent path="/page-b" page="PageB" minDelay={1200} />
		</Router>
	</Outer>
)
  1. I visit /page-a
  2. I click a <Link /> to /page-b
  3. UniversalComponent renders my NotFound component, with the error supplied in the props
  4. I navigate back to /page-a
  5. I click the same <Link /> to /page-b
  6. UniversalComponent successfully fetches and renders the page, as expected.

(I'm using @reach/router, not sure if this is relevant)

If I add a console.log(config) on line 34 of the bundled dist/requireUniversalModule.js:

  var config = getConfig(isDynamic, universalConfig, options, props);
  var chunkName = config.chunkName,
      path = config.path,
      resolve = config.resolve,
      load = config.load;
  console.log(config)

config is never supplied with a load function.

Is there anything obvious I might be missing? I'm not sure where to start debugging this. The code is in a client project so I can't share right now - but if this is a difficult problem I can make a stripped down repo.

webpack/client.local.js
module.exports = {
	name: 'client',
	target: 'web',
	devtool: 'inline-source-map',
	mode: 'development',
	entry: [
		'@babel/polyfill',
		'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=false&quiet=false&noInfo=false',
		'react-hot-loader/patch',
		path.resolve(__dirname, '../src/client/index.js'),
	],
	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
		path: path.resolve(__dirname, '../build/client'),
		publicPath: '/public/',
	},
	resolve: {
		symlinks: false,
		alias: {
			Components: path.resolve(__dirname, 'src', 'client', 'Components'),
			Views: path.resolve(__dirname, 'src', 'client', 'Views'),
			Utils: path.resolve(__dirname, 'src', 'client', 'Utils'),
			Types: path.resolve(__dirname, 'src', 'client', 'Types'),
			'test-utils': path.resolve(__dirname, 'src', 'client', 'Types'),
		},
		extensions: ['.js'],
	},
module: {
	rules: [
		{
			test: /\.jsx?$/,
			exclude: /node_modules/,
			use: [
				{
					loader: 'babel-loader',
				},
			],
		},
		{
			test: /\.svg$/,
			use: [
				{
					loader: 'babel-loader',
				},
				{
					loader: 'react-svg-loader',
					options: {
						jsx: true, // true outputs JSX tags
					},
				},
			],
		},
	],
},
plugins: [
	new WriteFilePlugin(),

	new webpack.HotModuleReplacementPlugin(),
	new webpack.NoEmitOnErrorsPlugin(),
	new webpack.DefinePlugin({
		'process.env': {
			IS_SSR: true,
			NODE_ENV: JSON.stringify('development'),
		},
	}),
],

}

webpack/server.local.js
const config = {
	name: 'server',
	devtool: 'source-map',
	target: 'node',
	// node: {
	// 	__dirname: false,
	// },
	resolve: {
		symlinks: false,
		alias: {
			Components: path.resolve(__dirname, 'src', 'client', 'Components'),
			Views: path.resolve(__dirname, 'src', 'client', 'Views'),
			Utils: path.resolve(__dirname, 'src', 'client', 'Utils'),
			Types: path.resolve(__dirname, 'src', 'client', 'Types'),
			'test-utils': path.resolve(__dirname, 'src', 'client', 'Types'),
		},
		extensions: ['.js'],
	},
module: {
	rules: [
		{
			test: /\.jsx?$/,
			exclude: /node_modules/,
			use: [
				{
					loader: 'babel-loader',
				},
			],
		},
		{
			test: /\.svg$/,
			use: [
				{
					loader: 'babel-loader',
				},
				{
					loader: 'react-svg-loader',
					options: {
						jsx: true, // true outputs JSX tags
					},
				},
			],
		},
	],
},
mode: 'development',
entry: ['regenerator-runtime/runtime.js', entry],
externals,
output: {
	path: output,
	filename: '[name].js',
	libraryTarget: 'commonjs2',
},

plugins: [
	new WriteFilePlugin(),
	new webpack.optimize.LimitChunkCountPlugin({
		maxChunks: 1,
	}),
	new webpack.DefinePlugin({
		'process.env': {
			NODE_ENV: JSON.stringify('development'),
		},
	}),
],

}

.babelrc
module.exports = { presets: [ '@babel/preset-flow', [ '@babel/preset-env', { modules: false, targets: { browsers: ['last 3 versions'], }, }, ], '@babel/preset-react', ], env: { development: { plugins: ['flow-react-proptypes'], }, test: { presets: [ [ '@babel/preset-env', { modules: 'commonjs', }, ], ], }, }, plugins: [ 'universal-import', 'babel-plugin-styled-components', [ 'babel-plugin-module-resolver', { root: ['./'], alias: { Components: './src/client/Components', Views: './src/client/Views', Utils: './src/client/Utils', Types: './src/client/Types', Services: './src/client/services', Queries: './src/client/Queries', 'test-utils': './jest/test-utils.js', }, }, ], '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-export-default-from', '@babel/plugin-syntax-dynamic-import', 'react-hot-loader/babel', 'lodash', 'ramda', ], }

If I do:

import './PageA'
import './PageB'

at the top of App.js, it works. But, of course, it means these modules are not split out into separate chunks. (I think?)

OK, I got this working. I'm not sure what was different about my setup that caused the problem in the first place. The fix was changing determineHowToLoad, from:

const determineHowToLoad = props =>
  Promise.all([
    import(/* webpackChunkName: '[request]' */ `./${props.page}`)
  ]).then(proms => proms[0])

to simply:

const determineHowToLoad = (props) => import(`./${props.page}`)

referencing: https://github.com/faceyspacey/universal-demo/blob/master/src/client/UniversalComponent.tsx#L9

I first I would start with using the <Component page={()=>import()}/>

Rather than strings. Honestly I never use strings anymore because resolving stuff is a nightmare

Lol as I read thru I see you came to the conclusion. Check out the RFR demo or look under my profile for a repo called Icarus. Then find the UniversalComponent.js file

Your implementation is correct but you might be able to just paste the one I use in and get nice results. Are you using our Babel plugin?

Yes, I'm using the plugin. I like the string-or-import option in your Icarus <UniversalComponent />.

Right now the only components I'm importing are in one directory (/Views), but I'll be digging back in soon to get smarter about how I'm splitting everything up.

Thanks for the quick response, and for a great utility!

its been a tough journey and we have faced some hardships, being the worlds first developers to bring this to market. It means a lot that so many still use our work.

Please let me know your progress and I’m willing to poersonally help my dependents. You get stuck - hit me up with a repo. Ive seen 100s if not 1000s of repos by now and know near everything that can go wrong

I cloned universal demo and tried to implement routes.js and I got same error.

const determineHowToLoad = ({ page }) =>
  typeof page !== "string" ? () => page() : import(`../pages/${page}/${page}`);

const UniversalComponent = universal(
  (props) => import(`../pages/${props.page}/${props.page}`),
  {
    onLoad: () => {   
      console.log("somethign");
    },
  }
);

When I switched to this
const determineHowToLoad = (props) => import(./${props.page})
error is gone but I cannot navigate. any endpoint only shows the homepage.

export const indexFromPath = (path) => { path = path === "/" ? "/HomePage" : path; //path starts with "/" so we omit it return pages.indexOf(path.substr(1)); };

I could not figure out how to implement routes with using universal component. It is a great a package and i wanna fully learn it.

Also onLoad function is not working :(
Thank You!