I googled it but still don't know how to use import() along with react-router-dom (version 4.x) to achieve code-splitting/async-route effect.
I am using webpack2
The doc of react-router-dom use bundle-loader. But I thought import() is supported natively by webpack2. Is there a way to use import() directly?
Thanks
https://github.com/brotzky/code-splitting-react-webpack
https://brotzky.co/blog/code-splitting-react-router-webpack-2/
Checkout this out, I follow the steps and finish dynamic code require for my project.
Anyway, I figure it out myself.
It kind of works at the moment. But I've test it in real world. APPRECIATE to point out bugs in the snippet.
Thanks.
First have a component like this
//#flow
import React from "react";
import { Loading } from "../loading";
let map = {};
export function AsyncLoad(props: {
moduleKey: string,
loadedFrom: Promise<any>,
onLoaded: () => void
}) {
let Comp = map[props.moduleKey];
if (Comp) {
let passedProp = Object.assign({}, props);
delete passedProp.moduleKey;
delete passedProp.loadedFrom;
return <Comp {...props} />;
} else {
processModule();
return <Loading />;
}
async function processModule() {
const mod = await props.loadedFrom;
map[props.moduleKey] = mod.default;
props.onLoaded();
}
}
Then in my App.js, do like this:
//#flow
import React, { PureComponent } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { AsyncLoad } from "./asyncLoad";
import "./app.scss";
export class App extends PureComponent {
render() {
return (
<Router>
<div>
<Link to="/quiz">Show Quiz</Link>
<div className="content">
<Route
path="/quiz"
component={() =>
<AsyncLoad
moduleKey="quiz"
loadedFrom={import("./quiz")}
onLoaded={() => {
this.forceUpdate();
}}
/>}
/>
</div>
</div>
</Router>
);
}
}
very simple
<Route path="/page3" render={() => (
<AC loader={import('./Page3')} />
)} />
class AC extends Component {
state = {
_: null
}
async componentDidMount() {
const { default: _ } = await this.props.loader;
this.setState({
_: <_ />
});
}
render() {
return (
<div>{this.state._}</div>
);
}
}
Related
I bought this template and am trying to understand which page getLayout in this code"{getLayout(<Component {...pageProps} />)}" goes to on initial load. I'm guessing it's a global variable somewhere, but I can't find it using the definitions. I'm trying to under the next.js documentation, but I'm having issues. If anyone has a good tutorial for this I'll happily take it.
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
import { SessionProvider } from 'next-auth/react';
import '#/assets/css/main.css';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer } from 'react-toastify';
import { ModalProvider } from '#/components/ui/modal/modal.context';
import ManagedModal from '#/components/ui/modal/managed-modal';
import ManagedDrawer from '#/components/ui/drawer/managed-drawer';
import DefaultSeo from '#/components/seo/default-seo';
import { SearchProvider } from '#/components/ui/search/search.context';
import PrivateRoute from '#/lib/private-route';
import { CartProvider } from '#/store/quick-cart/cart.context';
import SocialLogin from '#/components/auth/social-login';
import { NextPageWithLayout } from '#/types';
import QueryProvider from '#/framework/client/query-provider';
import { getDirection } from '#/lib/constants';
import { useRouter } from 'next/router';
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
function CustomApp({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
const authenticationRequired = Component.authenticationRequired ?? false;
const { locale } = useRouter();
const dir = getDirection(locale);
return (
<div dir={dir}>
<SessionProvider session={session}>
<QueryProvider pageProps={pageProps}>
<SearchProvider>
<ModalProvider>
<CartProvider>
<>
<DefaultSeo />
{authenticationRequired ? (
<PrivateRoute>
{getLayout(<Component {...pageProps} />)}
</PrivateRoute>
) : (
getLayout(<Component {...pageProps} />)
)}
<ManagedModal />
<ManagedDrawer />
<ToastContainer autoClose={2000} theme="colored" />
<SocialLogin />
</>
</CartProvider>
</ModalProvider>
</SearchProvider>
</QueryProvider>
</SessionProvider>
</div>
);
}
export default appWithTranslation(CustomApp);
Basically, you can define a per component layout. In order to do so, when you are defining a component, you add a property named getLayout. Here's an example, for a better understanding.
// ...
const Component: React.FC = () => <div>
I have a custom layout
</div>
// Here we define a layout, let's imagine it's a component
// we have inside /layouts and we have previously imported it
Component.getLayout = (page: React.ReactElement) =>
<LayoutComponent>{page}</LayoutComponent>
Now, when a page is rendered (note that in a Next JS app, all pages are rendered as a children of what is inside _app.{js,jsx,ts,tsx}) your code checks if the prop getLayout has been defined or not. Then, it is basically calling such function, if exists, otherwise it renders the base component.
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.
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
[Updated] before someone marks this as duplicate, i find the answer to be sort of vague and brief
Functions are not valid as a React child. This may happen if you return a Component instead of from render
I have a react component like this
import React, { Component} from 'react';
import axios from 'axios';
import Modal from '../../component/UI/modal/modal.js'
import Aux from '../Aux.js'
const ErrorHandler = (WrappedComponent, axios) => {
return class extends Component {
state = {
error:null
}
componentDidMount () {
axios.interceptors.request.use(req => {
this.setState({error:null})
});
axios.interceptors.response.use(req => {
console.log(req)
}, error => {
this.setState({error:error})
});
}
errorConfirmedHandler = () => {
this.setStae({error: null})
}
render() {
return (
<Aux>
<Modal
order={this.state.error}>
purchasingHandlerClose={this.errorConfirmedHandler }
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props} />
</Aux>
);
}
}
}
export default ErrorHandler;
this is my modal component which might be causing the problem
import React, { Component } from 'react';
import Classes from './modal.css'
import Aux from '../../../HOC/Aux.js'
import Backdrop from '../Backdrop/backdrop.js'
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.order !== this.props.order || nextProps.children !== this.props.children;
}
render () {
console.log(this.props.children)
return (
<Aux>
<Backdrop show={this.props.order} purchasingHandlerClose={this.props.purchasingHandlerClose} />
<div className={Classes.Modal} style={{display: this.props.order ? 'block' : 'none'}}>
{this.props.children}
</div>
</Aux> )
}
}
export default Modal;
I am seeing the following error in my console
Functions are not valid as a React child. This may happen if you
return a Component instead of from render
[Question] Can someone explain me what causes this error as someone down voted and wrote a comment saying the code responsible is not here (and then he deleted his comment) since the post would be too long if paste everything here hence looking for someone who can just explain me this error)
Found the mistake in
import React, { Component} from 'react';
import axios from 'axios';
import Modal from '../../component/UI/modal/modal.js'
import Aux from '../Aux.js'
const ErrorHandler = (WrappedComponent, axios) => {
return class extends Component {
state = {
error:null
}
componentDidMount () {
axios.interceptors.request.use(req => {
this.setState({error:null})
});
axios.interceptors.response.use(req => {
console.log(req)
}, error => {
this.setState({error:error})
});
}
errorConfirmedHandler = () => {
this.setStae({error: null})
}
render() {
return (
<Aux>
<Modal
order={this.state.error}>
purchasingHandlerClose={this.errorConfirmedHandler }
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props} />
</Aux>
);
}
}
}
export default ErrorHandler;
I should have done
<Modal
order={this.state.error}
purchasingHandlerClose={this.errorConfirmedHandler }>
but I accidentally did this ( notice the > )
<Modal
order={this.state.error}>
purchasingHandlerClose={this.errorConfirmedHandler }
The function below is meant to check if user is authorized before rendering a route with react-router. I got the basic code from somewhere I forgot.
This was working fine when I had a simple synchronous check to local storage, but when I introduced an Axios call to the server things got messy.
I read a lot of the SO's questions on like issues, ie, promises not returning value and I seem to have modified my code to conform to the regular pitfalls.
I particularly used the last answer to this question to fix my setup, but the issue remains.
On the code below, the console.logs output the parameters correctly, meaning that the failure with related to the return statement.
The specific error is:
Error: AuthRoute(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
import axios from 'axios';
const PRIVATE_ROOT = '/account';
const PUBLIC_ROOT = '/';
const RenderRoute = ({response, isPrivate, Route, Redirect, component, ...props}) => {
console.log('is_logged', response.data.is_logged); // boolean
console.log('isPrivate', isPrivate); // boolean
console.log('response', response); // axios object
console.log('route', Route); // function route
console.log('component', component); // home component
console.log('props', {...props}); // route props
if (response.data.is_logged) {
// set last activity on local storage
let now = new Date();
now = parseInt(now.getTime() / 1000);
localStorage.setItem('last_active', now );
return isPrivate
? <Route { ...props } component={ component } />
: <Route { ...props } component={ component } />;
} else {
return isPrivate
? <Redirect to={ PUBLIC_ROOT } />
: <Route { ...props } component={ component } />;
}
}
const AuthRoute = ({component, ...props}) => {
const { isPrivate } = component;
let last_active_client = localStorage.getItem('last_active') ? localStorage.getItem('last_active') : 0;
let data = {
last_active_client: last_active_client
}
let getApiSession = new Promise((resolve) => {
resolve(axios.post('/api-session', data));
});
getApiSession.then(response => RenderRoute({response, isPrivate,Route, Redirect, component, ...props})
).catch((error) => {
console.log(error);
});
}
export default AuthRoute;
This is what worked for me after comments above. It was necessary to create a separate component class. It's not tremendously elegant, but works.
The code needs to be placed into componentWillReceiveProps() for it to update at each new props. I know componentWillReceiveProps() is being deprecated. I will handle that separately.
/auth/auth.js
import React from 'react';
import RenderRoute from './components/render_route';
const AuthRoute = ({component, ...props}) => {
let propsAll = {...props}
return (
<RenderRoute info={component} propsAll={propsAll} />
)
}
export default AuthRoute;
/auth/components/render_route.js
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
import axios from 'axios';
const PRIVATE_ROOT = '/account';
const PUBLIC_ROOT = '/';
class RenderRoute extends React.Component {
constructor (props) {
super(props);
this.state = {
route: ''
}
}
componentWillReceiveProps(nextProps, prevState){
const { isPrivate } = this.props.info;
let last_active_client = localStorage.getItem('last_active') ? localStorage.getItem('last_active') : 0;
let data = {
last_active_client: last_active_client
}
axios.post('/api-session', data).then(response => {
let isLogged = response.data.is_logged;
if(response.data.is_logged) {
// set last activity on local storage
let now = new Date();
now = parseInt(now.getTime() / 1000);
localStorage.setItem('last_active', now );
this.setState({ route: isPrivate
? <Route { ...this.props.propsAll } component={ this.props.info } />
: <Route { ...this.props.propsAll } component={ this.props.info } />
})
} else {
this.setState({ route: isPrivate
? <Redirect to={ PUBLIC_ROOT } />
: <Route { ...this.props.propsAll } component={ this.props.info } />
})
}
}).catch((error) => {
console.log(error);
});
}
render() {
return (
<div>
{this.state.route}
</div>
)
}
}
export default RenderRoute;
So I got the "Roboto-Black" is not a system font, try putting through Expo.Font.loadAsync() error. but I've read in other posts that some people are having similar problems and the solution is by using await/promise.
However, Im not sure how to do that with my code since I'm just following a tutorial to do this and this is my first attempt using create-react-native-app with minimum JS knowledge :(
Please help me inspect this code snippet and let me know what I did wrong:
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Font, AppLoading } from 'expo';
import Router from 'loginDemo/config/routes.js'
import store from 'loginDemo/redux/store.js';
function cacheFonts(fonts) {
return fonts.map(function(font) {return Font.loadAsync(font)});
}
export default class App extends Component {
constructor() {
super();
this.state = {
isReady: false,
}
}
async _loadAssetsAsync() {
const fontAssets = cacheFonts([
{RobotoBlack: require('loginDemo/assets/fonts/Roboto-Black.ttf')},
{RobotoBold: require('loginDemo/assets/fonts/Roboto-Bold.ttf')},
{RobotoMedium: require('loginDemo/assets/fonts/Roboto-Medium.ttf')},
{RobotoRegular: require('loginDemo/assets/fonts/Roboto-Regular.ttf')},
{RobotoLight: require('loginDemo/assets/fonts/Roboto-Light.ttf')}
]);
await Promise.all([...fontAssets]);
}
render() {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._loadAssetsAsync}
onFinish={() => this.setState({isReady: true})}
onError={console.warn}
/>
);
}
return (
<Provider store={store}>
<Router/>
</Provider>
);
}
}