ReactJS - Async Dynamic Component Loading - reactjs

Situation
I receive json from a cms that describes the content that needs to display on any given page. This project has 50+ components so rather than require all of them on every page I'd rather cherry pick them as needed.
Question
How can I
Make sure all components are available for import (I assume this requires some webpack trickery)
When converting the json's content node to jsx, making sure that any component described is rendered out.
Current Thoughts
I can loop through the raw jsx and collect all the tags for a given page then attempt a load for each tag via something like
const name = iteration.tagName;
dynCmps[name] = someAsynchronousLoad(path + name);
Then dispatch a redux event when loading is complete to kick off a fresh render of the page.
As for converting raw text content to react js I'm using ReactHtmlParser
best resources so far
Dynamic loading of react components
http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack/

This had me stumped for a couple of days. After chatting with a colleague about it for some time it was decided that the amount of work it would take to offload the performance hit of loading all the components upfront is not work it for our scenario of 30-50 components.
Lazy loading CAN BE used but I decided against it as the extra 10ms of loading (if that) isn't going to be noticeable at all.
import SomeComponent from "./SomeComponent.js"
const spoofedComponents = {
SomeComponent: <SomeComponent />
}
const replaceFunc = (attribs, children) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
// lower case is important here because react converts everything to lower case during text-to-html conversion - only react components can be camel case whereas html is pascal case.
if(attribs.name === key.toLowerCase()) {
return spoofedComponents[key];
}
}
return <p>unknown component</p>
}
...
//inside render
const raw = "<SomeComponent><SomeComponent />"
// it's VERY important that you do NOT use self-closing tags otherwise your renders will be incomplete.
{parse(raw, {
replace: replaceFunc
})}
In my case I have 30+ components imported and mapped to my spoofedComponents constant. It's a bit of a nuissance but this is necessary as react needs to know everything about a given situation so that the virtual dom can do what it is supposed to - save on display performance. The pros are that now a non-developer (editor) can build a layout using a WYSIWYG and have it display using components that a developer made.
Cheers!
Edit
I'm still stuck on adding customized props & children.
Edit
Basic props are working with
const spoofedComponents = {
SomeComponent: (opts) => {
let s = {};
if(opts.attribs.style)
s = JSON.parse(opts.attribs.style);
if(opts.attribs.classname) {
opts.attribs.className = opts.attribs.classname;
delete opts.attribs.classname;
}
return <APIRequest {...opts.attribs} style={s}>{opts.children[0].data}</APIRequest>
}
}
...
const replaceFunc = (opts) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(opts.name === key.toLowerCase()) {
const cmp = spoofedComponents[key](opts);
return cmp;
}
}
return <p>unknown component</p>
}
Now to figure out how to add child components dynamically..
EDIT
This is working well enough that I'm going to leave it as is. Here is the updated replaceFunc
const replaceFunc = (obj) => {
const keys = Object.keys(spoofedComponents);
for(var i in keys) {
const key = keys[i];
if(obj.name === key.toLowerCase()) {
if(obj.attribs.style)
obj.attribs.style = JSON.parse(obj.attribs.style);
if(obj.attribs.classname) {
obj.attribs.className = obj.attribs.classname;
delete obj.attribs.classname;
}
return React.createElement(spoofedComponents[key], obj.attribs, obj.children[0].data)
}
}
return obj; //<p>unknown component</p>
}

Related

How can I traverse React Component Tree just using __REACT_DEVTOOLS_GLOBAL_HOOK__

I am trying to traverse through React Component Tree( or somehow get access to all components)
and create my own custom UI inside the browser(most likely an extension) to manipulate different components.
this is my code:
// const root_node = root._internalRoot.current;
const root_node = document.getElementById('root')
const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.get(1);
const rendered_root_node = renderer.findFiberByHostInstance(root_node);
But it's returning null. I tried using findHostInstanceByFiber as well.
Is there any way to traverse through the components in the browser?
Here is an example of how you can traverse the React component tree using the renderers property of the __REACT_DEVTOOLS_GLOBAL_HOOK__ object:
function traverseComponentTree(renderer, fiberNode, callback) {
callback(fiberNode);
let child = fiberNode.child;
while (child) {
traverseComponentTree(renderer, child, callback);
child = child.sibling;
}
}
const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.get(1);
const rootFiber = renderer.mounts[0];
traverseComponentTree(renderer, rootFiber, (fiberNode) => {
console.log(fiberNode.stateNode); // this will give you the instance of the component
console.log(fiberNode.type); // this will give you the type of the component
});
Please keep in mind that this code is intended for development use only, as the __REACT_DEVTOOLS_GLOBAL_HOOK__ is not available in production mode.
Also, If you are using the latest version of react, it's important to know that the __REACT_DEVTOOLS_GLOBAL_HOOK__ has been removed.

Handle heavy resource/DOM on React Portal

I have created a react portal inside my application to handle the use of Modal. The portal target is outside of my React root div as sibling of my root element.
<html>
<body>
<div id="root">{{app.html}}</div>
<div id="modal-root">
<div class="modal" tabIndex="-1" id="modal-inner-root" role="dialog">
</div>
</div>
</body>
</html>
So my Portal contents renders outside of the react application and its working fine. Here is my react portal code
const PortalRawModal = (props) => {
const [ display, setDisplay ] = useState(document.getElementById("modal-inner-root").style.display)
const div = useRef(document.createElement('div'))
useEffect(()=> {
const modalInnerRoot = document.getElementById("modal-inner-root")
if(validate(props.showModalId)) {
if( props.showModalId == props.modalId && _.size(props.children) > 0 ) {
setDisplay("block");
if(_.size(modalInnerRoot.childNodes) > 0) {
modalInnerRoot.replaceChild(div.current,modalInnerRoot.childNodes[0]);
} else {
modalInnerRoot.appendChild(div.current);
}
div.current.className = props.modalInner;
document.getElementById("modal-root").className = props.modalClassName;
document.body.className = "modal-open";
} else {
document.getElementById("modal-root").className = props.modalClassName;
if(div.current.parentNode == modalInnerRoot) {
modalInnerRoot.removeChild(div.current);
div.current.className = "";
}
}
} else {
setDisplay("none");
document.getElementById("modal-root").className = "";
if(div.current.parentNode == modalInnerRoot) {
modalInnerRoot.removeChild(div.current).className = "";
}
document.body.className = "";
}
},[ props.showModalId ])
useEffect(()=> {
document.body.className = display == "none" ? "" : "modal-open";
document.getElementById("modal-inner-root").style.display = display;
return () => {
if(!validate(props.showModalId)) {
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none"
}
};
},[ display])
useEffect(()=> {
if(_.size(props.children) <= 0){
modalInnerRoot.removeChild(div.current)
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none";
}
return () => {
if(_.size(props.children) <= 0){
modalInnerRoot.removeChild(div.current)
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none";
}
}
},[props.children, props.showModalId])
return ReactDOM.createPortal(props.children ,div.current);
}
Whenever the children are passed and modal is mounted, The heavy DOM is painted with little delay. But the same markup takes time, or even crashes the browser tab. Where am I going wrong in handling the heavy DOM operations? Or is there any async way to handle the heavy DOM operations that wont effect the overall performance?
Couple of reasons can attribute for this :
The last effect will always run for every re-render as props.children is an object and hence even if same children was passed again, it'll be a new object.
Direct DOM manipulation is an anti-pattern, as React maintains several DOM references in memory for fast diffing, hence direct mutation may result in some perf hit.Try writing the same in a declarative fashion.
Extract out the portal content into another sub-component and avoid DOM manipulations wherever possible.
One place would be :
if (_.size(props.children) <= 0) {
modalInnerRoot.removeChild(div.current);
}
can be replaced within the render function like :
{React.Children.count(props.children) ? <div /> : null}
You just have to use the modal root as the createPortal host div (the second argument). React will just render there instead of in the regular element.
Then if you need to "manipulate" the HTML, just use plain React. It does not work any differently inside of portaled elements. All createPortal does is tell React to take this part of the tree and attach it under specified element.
Just include a permanent empty div with no style (no need to use any display rules), and it will just receive all HTML you want to render. Then you make the content inside the modal root fixed, but not the modal root itself.
Don't do this:
const div = useRef(document.createElement('div'))
// all kinds of manipulation of this div
return ReactDOM.createPortal(props.children ,div.current);
Do this instead:
const modalRoot = document.getElementById('modal-root');
function ModalWrapper({children}) {
return <div class="modal" id="modal-inner-root" role="dialog">
{ children }
</div>
}
function PortalModal({children}) {
return React.createPortal(
<ModalWrapper>{ children }</ModalWrapper>,
modalRoot
}
function App() {
const [hasConfirmed, setHasConfirmed] = useState(false);
return <div>
// ...
{ !hasConfirmed && <PortalModal>
<h1> Please confirm </h1>
<button onClick={() => setHasConfirmed(true)}>
Yes
</button>
</PortalModal> }
</div>
}
You can perfectly manage the state of the component in the modal, and whether it should show the modal at all. You won't need to do any manual DOM manipulation anymore.
Once you stop rendering the portal, React removes it for you.
Why does the question's code have performance issues?
It does many DOM operations which, among other things, will result in style recalculations. You shouldn't have to do manual DOM operations at all, it's exactly what React is built to handle for you. And it's reasonably efficient at it.
Since you're doing it in useEffect, React has already triggered style recalculations and the result of that was painted to the screen. This is now immediately invalidated, and the browser needs to recalculate some amount of elements.
Which amount of elements?... All of them, because a style recalculation on the body is triggered by changing its classname.
document.body.className = "modal-open";
If you have a heavy DOM, a full style recalculation can quickly take long and cause noticeable stutter. You can avoid this by not touching the body and just adding an overlay div you can show and hide.
Can it cause a tab to crash though? Maybe in extreme cases, but probably not.
It's more likely that you're calling this component in a way that creates an infinite loop. Or you may be ending up doing a ridiculous amount of DOM operations. It's impossible to tell without the full code used when the performance issues were noted.

React components get duplicated on resize/re-render even though they have unique keys

Can you please advise what is missing in my code (https://codesandbox.io/s/x2q89v613o) that causes copies of components to be created on resize even though I had assigned unique keys to them?
Project is simple scheduler table with each cell being a component and event is also component. Some complexity added by using React Drag and Drop .. could it be that using HOC wrapper makes React do not recognize existing elements?
Thanks!!
VB
Add your componentWillReceiveProps with below in WeekView, I have added a line cell.events= [];. This is clear all previous events and add the new which came in props.
componentWillReceiveProps(nextProps) {
if (nextProps.events && nextProps.events.length) {
console.log("weekView componentWillReceiveProp got events");
this.state.cells.forEach(cell=>{
cell.events= [];
});
nextProps.events.forEach(x => {
const start = x.start;
const cellId = "c" + "_" + moment(start).valueOf();
const target = Helper.getItemFromArray(this.state.cells, cellId, "id");
if (target) {
console.log("found");
target.events.push(x);
}
});
console.log(
this.state.cells.filter(x => {
if (x.events.length > 0) return x;
})
);
}
}
The below line is causing the problem
tableWidth: ReactDOM.findDOMNode(this._tableTarget).offsetWidth
offsetWidth is changing on screen size change and hence creating a copy.
Remove it and try.
tableWidth: "auto"
or
tableWidth: "100%"

How use N views in sample app in react native

I have some doubt in React Native. I read this answer (How to do a multi-page app in react-native?) but I stayed with a little bit doubt:
I use Navigator to many views in React Native App, but how I do to N componentes? For example, if I have 5 different views I have use the before code five times... and n times?
to ellude more on my comment.
instead of this
if (routeId === 'SplashPage') {
return (
<SplashPage
navigator={navigator}/>
);
}
if (routeId === 'LoginPage') {
return (
<LoginPage
navigator={navigator}/>
);
}
just have a hashtable that you use to get the component dynamically.
const Component = VIEW_COMPONENTS[routeid];
so your code would look something like this
const VIEW_COMPONENTS = {
'SplashPage': SplashPage,
'LoginPage': LoginPage
};
renderScene = ( route, navigator ) => {
const ViewComponent = VIEW_COMPONENTS[route.id];
return <ViewComponent navigator={navigator}/>
}
any additional screen would be a single line entry to your lookup table. I have a native app with 40 screens and its very easy to manage like this
to add to this. you can abstract more out of this too. make every view not care about where it is used or what its next view is. Instead make all of that a part of your lookup object. specify a next route that every view can show. you can pass any additional information all of which is configurable and your screens can be reused on multiple flows!

React JS: Loop through a multi-dimensional array and determine/load the correct component

Using React, I'm fetching data from an API, a sample of which can be seen here.
I need to loop through the body section (multi-dimensional array) and then determine what type of 'block' it is:
heading
text
quote
frameImgeBlock
quoteImageBlock
frameQuoteBlock
Depending which block type it is then I need to create/load the corresponding React component (as I'm imagining it's better to separate these into individual React components).
How is it best to approach this in React/JS?
I may be able to tweak the API output a little if someone can suggest an easier approach there.
You could use a "translation" function like so, which would call external components:
// Import external deps
import Heading from './src/heading';
// Later in your component code
function firstKey(obj) {
return Object.keys(obj)[0];
}
getDomItem(item) {
const key = firstKey(item);
const val = item[val];
switch (key) {
case "heading":
return <Heading heading={ val.heading } ...etc... />
case "other key..."
return <OtherElm ...props... />
}
}
render() {
// data is your object
return data.content.body.map(item =>
this.getDomItem(item[firstKey(item)])
);
}

Resources