Dynamically importing components in ReactJS - reactjs

I have just been getting my hands dirty with react-js and have come across this piece of code for dynamically importing components in my app which I cant seem to understand?
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Dynamic extends Component {
constructor(props) {
super(props);
this.state = { module: null };
}
componentDidMount() {
const { path } = this.props;
import(`${path}`)
.then(module => this.setState({ module: module.default }))
}
render() {
const { module: Component } = this.state; // Assigning to new variable names #see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
return(
<div>
{Component && <Component />} // the code i can't figure out
//{Component} works fine too
//{<Component />} gives error
</div>
)
}
}
ReactDOM.render(<Dynamic path='./FirstComponent' />, document.getElementById('root'));
Can someone please explain the line of code which i have highlighted it looks to me some kind of conditional rendering but as far as i know it works like if the left hand evaluates to true the right hand is rendered but why is this code working with only {Component} as well?

Because at initial render {Component} evaluates to null.
As you have used destructuring.
const { module: Component } = this.state;
so
Component = null
But when you use <Component/> at initial render there is no <Component/> component. So using { <Component />} gives error.
Using Component and <Component/> are different.

Related

How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?

So I have a SSR app using Next.js. I am using a 3rd party component that utilizes WEB API so it needs to be loaded on the client and not the server. I am doing this with 'two-pass' rendering which I read about here: https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
I'm trying to figure out why when 'ssrDone' state changes in the next.js page state the entire <Layout> component unnecessarily re-renders which includes the page's Header, Footer, etc.
I've read about React.memo() as well as leveraging shouldComponentUpdate() but I can't seem to prevent it from re-rendering the <Layout> component.
My console.log message for the <Layout> fires twice but the <ThirdPartyComponent> console message fires once as expected. Is this an issue or is React smart enough to not actually update the DOM so I shouldn't even worry about this. It seems silly to have it re-render my page header and footer for no reason.
In the console, the output is:
Layout rendered
Layout rendered
3rd party component rendered
index.js (next.js page)
import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
ssrDone: false
};
}
componentDidMount() {
this.setState({ ssrDone: true });
}
render() {
return (
<Layout>
{this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
</Layout>
);
}
}
export default Home;
ThirdPartyComponent.jsx
import React from "react";
export default function ThirdPartyComponent() {
console.log("3rd party component rendered");
return <div>3rd Party Component</div>;
}
Layout.jsx
import React from "react";
export default function Layout({ children }) {
return (
<div>
{console.log("Layout rendered")}
NavBar here
<div>Header</div>
{children}
<div>Footer</div>
</div>
);
}
What you could do, is define a new <ClientSideOnlyRenderer /> component, that would look like this:
const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
initialSsrDone = false,
renderDone,
renderLoading,
}) {
const [ssrDone, setSsrDone] = useState(initialSsrDone);
useEffect(
function afterMount() {
setSsrDone(true);
},
[],
);
if (!ssrDone) {
return renderLoading();
}
return renderDone();
});
And you could use it like this:
class Home extends React.Component {
static async getInitialProps({ req }) {
return {
isServer: !!req,
};
};
renderDone() {
return (
<ThirdPartyComponent />
);
}
renderLoading() {
return (<div>Loading...</div>);
}
render() {
const { isServer } = this.props;
return (
<Layout>
<ClientSideOnlyRenderer
initialSsrDone={!isServer}
renderDone={this.renderDone}
renderLoading={this.renderLoading}
/>
</Layout>
);
}
}
This way, only the ClientSideOnlyRenderer component gets re-rendered after initial mount. đź‘Ť
The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)
I had a similar issue recently, what you can do is to use redux to store the state that is causing the re-render of the component.
Then with useSelector and shallowEqual you can use it and change its value without having to re-render the component.
Here is an example
import styles from "./HamburgerButton.module.css";
import { useSelector, shallowEqual } from "react-redux";
const selectLayouts = (state) => state.allLayouts.layouts[1];
export default function HamburgerButton({ toggleNav }) {
let state = useSelector(selectLayouts, shallowEqual);
let navIsActive = state.active;
console.log("navIsActive", navIsActive); // true or false
const getBtnStyle = () => {
if (navIsActive) return styles["hamBtn-active"];
else return styles["hamBtn"];
};
return (
<div
id={styles["hamBtn"]}
className={getBtnStyle()}
onClick={toggleNav}
>
<div className={styles["stick"]}></div>
</div>
);
}
This is an animated button component that toggles a sidebar, all wrapped inside a header component (parent)
Before i was storing the sidebar state in the header, and on its change all the header has to re-render causing problems in the button animation.
Instead i needed all my header, the button state and the sidebar to stay persistent during the navigation, and to be able to interact with them without any re-render.
I guess now the state is not in the component anymore but "above" it, so next doesn't start a re-render. (i can be wrong about this part but it looks like it)
Note that toggleNav is defined in header and passed as prop because i needed to use it in other components as well. Here is what it looks like:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
I'm using an id and fn because all my layouts are stored inside an array in redux, but you can use any logic or solution for this part.

Flow React: Cannot create element because React.Component [1] is not a React component

I just started to make use of flow a few weeks ago and from a week ago i've been getting a flow error on which i've no idea how to fix.
The code goes like this:
// #flow
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";
import CookieStorage from "./../services/CookieStorage";
import type { Component as ComponentType } from "react";
type Props = {
component: ComponentType<any, any>
}
class ProtectedRoute extends Component<Props> {
render() {
const isAuthenticated = this.isAuthenticated();
const {...props} = this.props;
const AuthorizedComponent = this.props.component;
return (
<Route
{...props}
render={props => (
isAuthenticated ?
<AuthorizedComponent {...props} /> :
<Redirect to="/"/>
)}
/>
);
}
isAuthenticated(): boolean {
const data = CookieStorage.get("foobar");
return data !== null;
}
}
export default ProtectedRoute;
In here flow throws this error:
Error:(23, 8) Cannot create `AuthorizedComponent` element because `React.Component` [1] is not a React component.
I don't know if i am doing a wrong import type or a wrong type declaration for the component that is to be rendered when the authentication example is ok.
I've copied this code from a website i don't remember where, but he was making use of this snippet using const {component: Component} = this.props and render it as <Component {...props} /> which for me it seems a little ambiguous, which is why i changed the declaration a bit to make it easy to understand when reading, but still even doing the exact same code like the snipped where i copied this code, flow still throws that error.
I've made a gist of this in case someone would knows a solution for this and would like to make a change, if no one is able to help me fix this in here, then i will send a ticket issue to their project using this gist
Try to use React.ComponentType instead?
import type { ComponentType } from "react";
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";
import CookieStorage from "./../services/CookieStorage";
type Props = {
component: ComponentType<any>
}
class ProtectedRoute extends Component<Props> {
render() {
const isAuthenticated = this.isAuthenticated();
const { component: AuthorizedComponent, ...props } = this.props;
return (
<Route
{...props}
render={props => (
isAuthenticated ?
<AuthorizedComponent {...props} /> :
<Redirect to="/"/>
)}
/>
);
}
isAuthenticated(): boolean {
const data = CookieStorage.get("foobar");
return data !== null;
}
}
export default ProtectedRoute;
See https://flow.org/en/docs/react/types/#toc-react-componenttype

Routing in ReactJS with props

I have one main component where I have all state.
And here I passed this states to two different components.
The problem is - I need to open this two components in two different links (<TimeTracker />, <TimeCalendar />).
Render them separately.
How can I made it with React-router? Is it possible?
Bellow is my code for main component
export default class App extends React.Component {
constructor () {
super();
this.initStorage();
this.state = {
startTime: this.getStoreItem('startTime') || 0,
currentTask: this.getStoreItem('currentTask') || '',
results: this.getStoreItem('results') || [],
calendarResults: this.getStoreItem('calendarResults') || []
};
}
/**
create an object in localStorage for timer data if it is not present
*/
initStorage () {
let data = localStorage.getItem('timeData');
if (!data) {
localStorage.setItem('timeData', JSON.stringify({}));
}
}
/**
* get item value from storage
* #param key - item name
*/
getStoreItem = (key) => {
const data = JSON.parse(localStorage.getItem('timeData'));
return data[key];
}
/**
* change item value in storage
* * #param key - item name
* #param value - new value for item
*/
setStoreItem = (key, value) => {
const data = JSON.parse(localStorage.getItem('timeData'));
data[key] = value;
localStorage.setItem('timeData', JSON.stringify(data));
this.setState({
[key]: value
});
}
render () {
const { startTime, currentTask, results, calendarResults } = this.state;
return (
<MuiThemeProvider>
<div>
<TimeTracker
results={results}
setStoreItem={this.setStoreItem}
startTime={startTime}
currentTask={currentTask} />
<TimeCalendar calendarResults={calendarResults} />
</div>
</MuiThemeProvider>
);
}
}
I am new in Routing and did not find some similar examples.
Please help to understand how to do it.
I can make routing for them, but if component do not have props.
But in my example I'm bewildered
Thank you in advance!
Here's an extract from my reactjs code that should help you out :
Router.jsx:
import React from 'react';
import { Router, Route } from 'react-router';
import createBrowserHistory from 'history/createBrowserHistory';
// route components
import { HomePage } from '../Pages/HomePage.jsx';
import { LoginPage } from '../Pages/LoginPage.jsx';
const browserHistory = createBrowserHistory();
export const renderRoutes = () => (
<Router history={browserHistory}>
<div>
<Route exact path="/" component={HomePage}/>
<Route exact path="/login" component={LoginPage}/>
</div>
</Router>
);
In this file, you start off by defining the components that will be rendered following the url adress you will visit on your page. Here is one of these two components:
HomePage.jsx :
import React, { Component } from 'react'
import { Menu, Segment } from 'semantic-ui-react'
import { AppBarEX } from '../components/Appbar.jsx'
export class HomePage extends Component {
render() {
return (
<div>
<AppBarEX />
</div>
)
}
}
HomePage is defined as the landing page in React Router thanks to the "/" path. So when you land on the website you will automatically be directed to the HomePage. In the <AppBarEX/> I use this:
import { Link } from 'react-router-dom'
<Link to = "/login">
The element allows you to define when and how you want to link to other pages, this is probably what you were looking for. In this situation the Link will send you to the login page. To place elements inside your link, you can wrap them within the Link: <Link> your element </Link>
Finally, the element you want to render in your main.jsx goes as follows :
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { renderRoutes } from './Router.jsx';
Meteor.startup(() => {
render(renderRoutes(), document.getElementById('main_body'));
});
This will allow you to render the renderRoutes defined in the router.jsx. You can find out more here:
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/02-rendering-a-route
Hope this helped you out!
D.

Dynamically adding components in react

I need to add components by rendering it in react:
<componentName ..... />
However, the name of component is not known and coming from a variable.
How I can render that?
You will need to store references to the dynamic components:
import ComponentName from 'component-name'
class App extends Component {
constructor(props) {
super(props)
this.components = {
'dynamic-component': ComponentName
}
}
render() {
// notice capitalization of the variable - this is important!
const Name = this.components[this.props.componentName];
return (
<div>
<Name />
</div>
)
}
};
render(<App componentName={ 'dynamic-component' } />, document.getElementById('root'));
See the react docs for more info.
You can create a file that exports all the different components
// Components.js
export Foo from './Foo'
export Bar from './Bar'
then import them all
import * as Components from './Components'
then you can dynamically create them based on a variable/prop:
render() {
// this.props.type = 'Foo'
// renders a Foo component
const Component = Components[this.props.type];
return Component && <Component />;
}
Okay, for me this worked:
import {Hello} from 'ui-hello-world';
let components = {};
components['Hello'] = Hello;
export default components;
and then in my class:
import customComps from '.....json';
....
let Component = Custom.default[customComps.componentName];
return (
<Component

React: dynamic import jsx

This question related to dynamically importing JSX files into React.
Basically we have one main component that dynamically renders other components based on a structure stored in a database. The dynamic components are stored in a subdirectory "./Components". We statically define the this as:
import CompA from './Components/CompA';
import CompB from './Components/CompB';
var components = {
'CompA': CompA,
'CompB': CompB
}
and we render them using:
var type = 'CompA'
var Component = components[type];
...
<Component />
While this works fine, it is a bit too static for us. We have 100+ components (CompA/CompBs) and statically define them does not work.
Is it possible to import all JSX files in "./Compontents" and populate the components-array?
And, what would be really (really) nice would be if we could send the "./Components" path as a prop to the main components. And the main component would use this path to import the .jsx files. Like this:
<MainComponent ComponentPath="./SystemComponents">
Would use "./SystemComponents" as path for .JSX files and:
<MainComponent ComponentPath="./UserComponents">
Would use "./UserComponents" as import path.
What about having a components/index.js with contents:
export CompA from "./comp_a";
export CompB from "./comp_b";
Then you do:
import * as Components from "./components"
then you would use as:
<Components.CompA />
<Components.CompB />
...
Hope this helps.
I doubt you can load anything when sending path through component props, loading of the file should then happen inside the React component lifecycle methods which is not something I would recommend.
As of React 16.6.0, we can lazy-load components and invoke them on-demand.
The Routing
// We pass the name of the component to load as a param
<Switch>
…
<Route path="/somewhere/:componentName" component={MyDynamicComponent} />
</Switch>
views/index.js
import { lazy } from 'react';
const SomeView = lazy(() => import('./SomeView'));
const SomeOtherView = lazy(() => import('./SomeOtherView'));
export { SomeView, SomeOtherView };
MyDynamicComponent.js
import React, { Suspense, Component } from 'react';
import { PropTypes } from 'prop-types';
import shortid from 'shortid'; // installed separately via NPM
import * as Views from './views';
class MyDynamicComponent extends Component {
render() {
const {
match: {
params: { componentName },
},
} = this.props;
const Empty = () => <div>This component does not exist.</div>;
const dynamicComponent = (() => {
const MyComponent = Views[`${componentName}View`]
? Views[`${componentName}View`]
: Empty;
return <MyComponent key={shortid.generate()} />;
})();
return (
<>
<Suspense fallback={<div>Loading…</div>}>{dynamicComponent}</Suspense>
</>
);
}
}
MyDynamicComponent.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
componentName: PropTypes.string.isRequired,
}),
}),
};
export default MyDynamicComponent;
Usage
{items.map(item => (
<NavLink to={`/somewhere/${item.componentName}`}>
{item.name}
</NavLink>
))}
To complement #gor181's answer, I can add that exports must be this way:
export { default as CompA } from "./comp_a";
export { default as CompB } from "./comp_b";
Hope this might be helpful.

Resources