React Router Async Routes and Webpack - reactjs

I am trying to use the React Router Async API to achieve some code splitting.
I have Currently a main route file and a subroute:
// routes/index.js
export const createRoutes = (store) => ({
path: '/',
component: AppView,
indexRoute: {
onEnter: (nextState, transition) => {
transition('/login')
},
},
childRoutes: [
LoginRoute(store),
]
})
And then my Login Route looks like this:
//routes/Login/index.js
export default (store) => ({
path: 'login',
/* Async getComponent is only invoked when route matches */
getComponent (nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure([], (require) => {
/* Webpack - use require callback to define
dependencies for bundling */
const requireUnauthentication = require('containers/UnauthenticatedComponent').default;
const LoginModalContainer = require('components/Login/LoginModalContainer').default;
/* Return getComponent */
cb(null, requireUnauthentication(LoginModalContainer))
/* Webpack named bundle */
}, 'login')
},
indexRoute: {
getComponent(nextState, cb){
require.ensure([], (require) => {
const LoginStart = require('components/Login/LoginStart').default;
cb(null, LoginStart);
}, 'login')
}
},
getChildRoutes: (location, cb) => {
require.ensure([], (require) => {
const routes = [
{ path: 'login-start', component: require('components/Login/LoginStart').default },
{ path: 'login-prompt', component: require('containers/LoginContainer').default },
{ path: 'phone-number', component: require('components/Register/PhonenumberSet').default }
];
cb(null, routes);
}, 'login');
}
})
The problem is when directly navigating to /login I am getting errors:
DOMLazyTree.js:62 Uncaught TypeError: Cannot read property 'replaceChild' of null
ReactDOMComponentTree.js:105 Uncaught TypeError: Cannot read property '__reactInternalInstance$nva6m7qemb9ackp2ti2ro1or' of null
I think this is related to the asynchronous route configuration, since as soon as I change something and hot reloading kicks in, everything loads fine.

Related

Storybook re-renders story after receiving MSW response

When I trigger requests to the backend within a Storybook story, the story is re-rendered after receiving the response from Mock Service Worker (MSW). How can I prevent the re-rendering from happening?
Here's the story:
I followed the tutorial on https://storybook.js.org/addons/msw-storybook-addon to set up MSW for my Storybook stories.
So I:
installed msw and msw-storybook-addon
generated a service worker: npx msw init static/
updated my preview.js to call the initialize() function, added the mswDecorator, and set some global handlers (e. g. for "/api/customers")
When opening a component in Storybook that includes a request to "/api/customers" the following happens:
Initially MSW is telling me that it's enabled: [MSW] Mocking enabled.
I click a button to manually send a request to "/api/customers"
MSW is telling me that this API request is covered: [MSW] 15:21:20 GET /api/customers/ (200 OK)
I print the request results on the console - that works and I can see the expected results printed
But right after that the story is unintentionally re-rendered - this is what I want to suppress.
FF is telling me:
The connection to http://localhost:6006/__webpack_hmr was interrupted
while the page was loading.
Chrome doesn't give me that piece of information.
Here's the setup:
package.json:
{
...
"msw": {
"workerDirectory": "static"
}
}
main.js (Storybook):
// imports ...
const toPath = (filePath) => path.join(process.cwd(), filePath);
module.exports = {
core: {
builder: "webpack5",
},
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.#(js|jsx|ts|tsx)"],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
],
features: {
emotionAlias: false,
},
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: custom.module.rules, // rules how to handle file extensions
},
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
"#emotion/core": toPath("node_modules/#emotion/react"),
"emotion-theming": toPath("node_modules/#emotion/react"),
},
},
};
},
staticDirs: ["../path/to/static/"], // that's our public
};
Here's some code:
preview.js
export const parameters = {
...
msw: {
handlers: [
rest.get("/api/customers", (req, res, ctx) => {
let customers = ...;
return res(
ctx.json({
data: customers,
})
);
}),
],
},
}
ExampleStory.stories.jsx:
export default {
title: "Customers",
};
const getCustomers = () => {
// ... set axios config etc.
axios.get("/api/customers", config)
.then((response) => response.data.data)
.then((customers) => console.log(customers)); // yep, the customers gets printed!
};
export const ExampleStory = () => {
return (
<div>
Simple demo story
<button onClick={getCustomers}>Get customers</button>
</div>
);
};
Node Modules:
Storybook#6.5.9
msw#0.42.3
msw-storybook-addon#1.6.3

NextJS - Routing with different language

I try to create multilanguage website with Next JS.
I'm using next-translate package for translation.
For the posts I use static generation.
|-> pages
|-> post
|-> [slug].js
|-> i18n.json
|-> next.config.js
Problem
When I use default language ('tr') I successfully navigate http://localhost:3000/post/title-tr
But If I changed to language to 'en', library add 'en' to path then try to navigate http://localhost:3000/en/post/title-en and returns 404 page.
Solution I tried
In next.config.js I add rewrites method. But It didn't work.
require('dotenv').config();
const nextTranslate = require('next-translate');
module.exports = nextTranslate({
async rewrites() {
return [
{
source: '/en/post/:path*',
destination: '/post',
},
]
},
env: {
NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,
},
})
Where should I solve this problem? Routing, config files or Link component?
i18n.json
{
"locales": ["tr", "en"],
"defaultLocale": "tr",
"pages": {
"*": ["common","country","position"]
}
}
Link component I used
<Link key={`L${item.id}`} href="/post/[slug]" as={`/post/${slug(item.title)}-${item.id}`}></Link>
[slug].js
function Post({ data }){
return <>...</>
}
export async function getStaticPaths() {
const { data } = await fetch('fromAPI');
return {
paths: data.map(item => {
return {
params:
{
slug: `${slug(item.title)}-${item.id}`
}
}
}),
fallback: false
}
}
export async function getStaticProps({ params }) {
const { data } = await fetch('fromAPI');
return { props: { data} }
}
export default Post;
You have to add a path for every locale as shown in the documentation: https://nextjs.org/docs/advanced-features/i18n-routing#dynamic-routes-and-getstaticprops-pages
// [slug].js
export const getStaticPaths = ({ locales }) => {
return {
paths: [
{ params: { slug: 'post-1' }, locale: 'tr' },
{ params: { slug: 'post-1' }, locale: 'en' },
],
fallback: true,
}
}
When I use default language ('tr') I successfully navigate http://localhost:3000/post/title-tr
But If I changed to language to 'en', library add 'en' to path then
try to navigate http://localhost:3000/en/post/title-en and returns 404
page.
If no locale is provided only the defaultLocale will be generated.
If fallback is false, then any paths not returned by getStaticPaths will result in a 404 page. So right now you're only generating the defaultLocale "tr" and all other paths with different locales will result in a 404 page.

Preparing requested page Gatsby

I'm using Gatsby as a front-end, and Strapi as my back-end to create my website. However, when I clicked my blog, it only shows white blank page, and when I checked it on my localhost, it shows these errors. Anyone have ever experienced this error?
Here is my Gatsby-node.js
const path = require("path")
// create pages dynamically
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
{
blogs: allStrapiBlogs {
nodes {
slug
}
}
}
`)
const prodres = await graphql(`
{
products: allStrapiProduct {
nodes {
slug
}
}
}
`)
result.data.blogs.nodes.forEach(blog => {
createPage({
path: `/blogs/${blog.slug}`,
component: path.resolve(`src/templates/blog-template.js`),
context: {
slug: blog.slug,
},
})
})
prodres.data.products.nodes.forEach(product => {
createPage({
path: `/product/${product.slug}`,
component: path.resolve(`src/templates/product-template.js`),
context: {
slug: product.slug,
},
})
})
}

The root path "/" does not match the reactjs declared route on server side rendering, when webpack middleware is used

While studying Reactjs Server Side Rendering, on a node Express web server, and Webpack middleware, I'm finding hard to understand why the root route "/" matching component is not passed into the html, while nested "/foobar" works fine (and you see the parents too).
If the webpack middleware's removed, the path "/" returns the match reactjs route.
Please find the source below, have in mind that there's a lot of testing and seeing and it's not quality code.
The Webpack development configuration file:
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'inline-source-map',
context: path.resolve(__dirname, 'src'),
entry: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'babel-polyfill',
'./js/index.js'
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
'babel-loader'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
use: [
'file-loader'
]
},
{
test: /\.(jpg|png|gif|svg)$/i,
use: [
'file-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, '/src/index.html'),
filename: 'index.html'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
// enable HMR globally
new webpack.HotModuleReplacementPlugin(),
// prints more readable module names in the browser console on HMR updates
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
The Reactjs Router related components.
The Rootjs:
import React from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'
import routes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<Router history={history}>
{ routes }
</Router>
</Provider>
)
}
export default Root
The Routes:
import React from 'react'
import { Route } from 'react-router'
import App from './containers/app'
import Foobar from './containers/foobar'
export default (
<Route path='/' component={App}>
<Route path='foobar' component={Foobar} />
</Route>
)
The Server.js:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import { match, RouterContext } from 'react-router'
import routes from './src/js/routes'
import configureStore from './src/js/store'
import App from './src/js/containers/app'
const app = express()
const router = express.Router()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging')))
var config = null
var fs = require('fs')
var htmlTemplateString = ''
/**
* Environment settings
*/
if (['staging', 'production'].indexOf(process.env.NODE_ENV) > -1) {
console.log('break 1')
dist = path.resolve(__dirname, process.env.NODE_ENV)
config = require('../config')
htmlTemplateString = fs.readFileSync(dist + '/index.html', 'utf-8')
} else {
console.log('break 2')
config = require('./config')
htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8')
}
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
router.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
// HMR only in development
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'staging') {
console.log('Development environment: Starting webPack middleware...')
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(webpackDevConfig)
var webpackDevMiddleware = require('webpack-dev-middleware')
var devMiddleware = webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true
}
})
router.use(devMiddleware)
router.use(webpackHotMiddleware(compiler, {
log: console.log
}))
// Production needs physical files! (built via separate process)
router.use('/assets', express.static(dist))
// any other is mapped here
router.get('*', (req, res, next) => {
console.log('req.url: ', req.url)
match({ routes, location: req.url }, (err, redirect, props) => {
if (props) {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<RouterContext {...props} />)
// Grab the initial state from our Redux store
const finalState = store.getState()
// Send the rendered page back to the client
let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml)
// Paste the state into the html
const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>`
html = html.replace('</head>', preloadedStateScript)
res.send(html)
} else {
res.status(404).send('Not found')
}
})
})
}
app.disable('x-powered-by')
app.use('/', router)
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})
I initially thought that had something to do with where the webpack middleware's set, but there's a typo somewhere in the initial code, so had to refactor and it works server side now:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './src/js/routes'
import configureStore from './src/js/store'
const app = express()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging')))
var config = null
var fs = require('fs')
var htmlTemplateString = ''
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(require('./webpack.dev.config'))
var webpackDevMiddleware = require('webpack-dev-middleware')
config = require('./config')
htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8')
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true
}
}))
app.use(webpackHotMiddleware(compiler, {
log: console.log
}))
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
app.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
app.use('/assets', express.static(dist))
// any other is mapped here
app.get('*', (req, res, next) => {
console.log('req.url', req.url)
match({ routes: routes, location: req.url }, (error, redirectLocation, props) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (props) {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<RouterContext {...props} />)
// Grab the initial state from our Redux store
const finalState = store.getState()
// Send the rendered page back to the client
let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml)
// Paste the state into the html
const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>`
html = html.replace('</head>', preloadedStateScript)
res.status(200).send(html)
} else {
res.status(404).send('Not found')
}
})
})
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})

Webpack Splitting React Components into "0.js" "1.js" etc. etc

I am unable to re-create the splitting of React components into separate files e.g. 0.js, 1.js, 2.js etc. for code-splitting and reducing the bundle.js file. Does anyone happen to know how this outcome would be produced? I tried recreating it with ChunkManifest and webpack-manifest plugins but it just wouldn't do it. Any advice would be awesome!
routes.js
function errorLoading(err) {
console.error('Dynamic page loading failed', err);
}
function loadRoute(cb) {
return (module) => cb(null, module.default);
}
export default [
{
path: '/',
component: App,
childRoutes: [
{
path: 'signup',
getComponent(location, cb) {
System.import('./modules/App/components/Authentication/Login.js')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
{
path: 'verify',
getComponent(location, cb) {
System.import('./modules/App/components/Authentication/Verify.js')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
{
path: 'password-reset',
getComponent(location, cb) {
System.import('./modules/App/components/Authentication/PasswordReset.js')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
{
path: 'new-password',
getComponent(location, cb) {
System.import('./modules/App/components/Authentication/NewPassword.js')
.then(loadRoute(cb))
.catch(errorLoading);
}
}
]
}
]
The kind of code splitting is accomplished in a few ways:
require.ensure()
System.import (This will be deprecated in webpack v3)
import()
Here is a link from our new docs page which specifies some examples of code splitting with react. https://webpack.js.org/guides/lazy-load-react/
(You can see here it is also referred to as lazy-loading modules)

Resources