Shadow DOM not showing in DOM - javascript

Following is code to mount shadow dom:
const template = makeTemplate() //helper function for styling etc
const host = document.createElement('custom-element')
const root = host.createShadowRoot()
document.body.appendChild(template)
document.body.appendChild(host)
root.appendChild(document.importNode(template.content, true))
This works, I can test my app's internal function calls by loggings, but nothing appears in dom. My app is made in ReactJS, thus i am able to log ComponentDidMount but cant see app in dom!
Note:
If I refresh the page after initial entry, then this exact process runs to mount, but extention load as expected. Only on first entry there is a issue!

Related

React, wait for DOM to update before unmounting?

I have a component that uses a js library. Since it's vanilla js I've added a bunch of dynamic eventListners that I want to remove when unmounting the component. I've set up a function to run on Blur or when clicking out side of the component so it would run document.getElementById and then clone that element and replace it. Right after that I update the state to let Parent component know to not render the component anymore so it unmounts.
What seems to be happening is the code to get, clone the element and replace it isn't happening right away and so the state to unmount is running and by the time the cloning and replacing happens the component i already unmounted so it cannot find that element in the DOM anymore. How can I can avoid this
const cleanUp = () => {
const element = document.getElementById(id);
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
setUnMount(true);
};

React - Lifecycle behaviour (componentDidMount) changes between local dev and production build

I am having a strange issue with my react app (created with create-react-app) When i am running locally everything works perfectly, but when i then run the app in production with npm run build componentDidMount (or just the lifecycle in general) starts acting up. Specifically, actions that do not cause a component to mount/re-mount in development does it in production (and these remounts are not wanted)
Some more specifics. I am creating an app where users can add draggable components (using react-draggable) to a screen to create views. These views are persistent so i store the component x+y coordinates in an array of objects and save them to storage. When the page loads, the array is fetched and the view is populated using the saved coordinates. When a component is moved, there is a callback to update it's coordinates in the array and save them to storage.
The code
Parent view component:
populateComponents = () => {
let components = this.state.componentPositions.map((c) =>
<DraggableFunctionComponent id={c.componentName} key={c.componentName}
x={c.x} y={c.y} moveEnabled={this.state.moveEnabled}
/>
this.setState({ components: components })
//In render
<div style={{ position: "absolute" }}>
{this.state.components}
</div>
And then in the DraggableFunctionComponent:
<Draggable disabled={!props.moveEnabled} position={{x: x, y:y}} onStop={handleStop}>
<div>
<MyOtherComponent /> //This does some API-calls etc. when mounted
</div>
</Draggable>
//Set initial position from what is saved in storage
useEffect(() => {
setX(props.x);
setY(props.y);
}, []);
Problem One
The "moveEnabled"-prop is used to disable movement of the Draggable. It is done for all components in the parent view component by changing the value of the "moveDisabled" state property. If this is changed locally, the component does not remount (which is desired). In production (npm run build/serve), however, all components are remounted when that prop is changed.
setMovement = () => {
const movement = this.state.movementEnabled;
this.setState({ movementEnabled: !movement }, this.populateComponents)
};
Problem Two
Adding a new component (which pushes a new component with x and y positions set to 0 in the state-held componentPositions array in the parent component) causes a full remount in production but not locally.
Problem Three
I've also noticed that componentDidMount is called multiple times (3) in production but only once locally when the page is reloaded. That is, if there is a component in the view it will (seemingly) mount 2 additional times. So, this behaviour doesn't seem to pertain to only when something in the state is actually changed by the user - it happens "by default".
Summary
I am not a react expert at all and there are probably lots of errors/problematic design patterns in the above code which very well could be the cause of this behaviour. However, i am confused as to why i don't see the results of this locally as well? I have found very little info on behavioural changes between local and built versions of React apps.

how to emulate messages/events with react useState and useContext?

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs

What's the difference between hydrate() and render() in React 16?

I've read the documentation, but I didn't really understand the difference between hydrate() and render() in React 16.
I know hydrate() is used to combine SSR and client-side rendering.
Can someone explain what is hydrating and then what is the difference in ReactDOM?
From the ReactDOMServer docs (emphasis mine):
If you call ReactDOM.hydrate() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
The text in bold is the main difference. render may change your node if there is a difference between the initial DOM and the current DOM. hydrate will only attach event handlers.
From the Github issue that introduced hydrate as a separate API:
If this is your initial DOM:
<div id="container">
<div class="spinner">Loading...</div>
</div>
and then call:
ReactDOM.render(
<div class="myapp">
<span>App</span>
</div>,
document.getElementById('container')
)
intending to do a client-side only render (not hydration).
Then you end with
<div id="container">
<div class="spinner">
<span>App</span>
</div>
</div>
Because we don't patch up the attributes.
Just FYI the reason they didn't patch the attributes is
... This would be really slow to hydrate in the normal hydration mode and slow down initial render into a non-SSR tree.
I don't have anything specific to add to what's been said above about the use of hydrate, but in trying to learn about it I put together a little example, so here's the work for whoever finds it helpful.
Goal
Serve two pages, one which uses ReactDOM.hydrate and one which uses ReactDOM.render. They will depend upon some react components written in JSX, which are loaded by <script> tags, given artificial delay (by the server) to illustrate the difference between hydrate and render.
Basic Structure
One file which has the HTML "skeleton"
One file with the custom React components written in JSX
One script which generates all pages for the server to use
One script to run the server
Results
After I generate the pages and run the server, I go to 127.0.0.1 and am presented with the header hydrate, a button, and two links. I can click the button, but nothing happens. After a few moments, the document finishes loading and the button starts counting my clicks. Then I click on the "render" link. Now, the page I'm presented with has the header render and two links, but no button. After a few moments, the button appears and is immediately responsive.
Explanation
On the "hydrate" page, all the markup is immediately rendered, because all the necessary html is served with the page. The button is unresponsive because there are no callbacks connected yet. Once components.js finishes loading, the load event fires from the window and the callbacks are connected with hydrate.
On the "render" page, the button markup isn't served with the page, but only injected by ReactDOM.render, so it isn't immediately visible. Note how the appearance of the page is jarringly changed by the script finally loading.
Source
Here is the custom react component I am using. It will be used by the server in node with react to statically render components, and it will also be loaded dynamically from the server for use in pages (this is the purpose of checking for exports and React objects at the beginning of the file).
// components.jsx
var exports = typeof(exports) == 'object' ? exports : {};
var React = typeof(React) == 'object' ? React : require('react');
function MyButton(props) {
[click, setClick] = React.useState(0);
function handleClick() { setClick(click + 1); }
return (
<button onClick={handleClick}>Clicked: {click}</button>
);
}
exports.MyButton = MyButton;
This is the script used to generate all the pages required for the server. First, babel is used to transpile components.jsx into javascript, then these components are used, along with React and ReactDOMServer, to create the actual pages. These pages are created with the fuction getPage which is exported from the file pageTemplate.js, shown next.
// genScript.js
let babel = require('#babel/core');
let fs = require('fs');
let ReactDOMServer = require('react-dom/server');
let React = require('react');
let pageTemplate = require('./pageTemplate.js');
script = babel.transformFileSync(
'components.jsx',
{presets : [['#babel/react']]}
);
fs.writeFileSync('components.js',script.code);
let components = require('./components.js');
hydrateHTML = pageTemplate.getPage(
'MyButton',
ReactDOMServer.renderToString(React.createElement(components.MyButton)),
'hydrate'
);
renderHTML = pageTemplate.getPage(
'MyButton',
'',
'render'
);
fs.writeFileSync('hydrate.html',hydrateHTML);
fs.writeFileSync('render.html',renderHTML);
This file just exports the getPage function mentioned previously.
// pageTemplate.js
exports.getPage = function(
reactElementTag,
reactElementString,
reactDOMMethod
) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js" defer></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" defer></script>
<script src="./components.js" defer></script>
</head>
<body>
<h1>${ reactDOMMethod }</h1>
<div id="react-root">${ reactElementString }</div>
hydrate
render
</body>
<script>
window.addEventListener('load', (e) => {
ReactDOM.${ reactDOMMethod }(
React.createElement(${ reactElementTag }),
document.getElementById('react-root')
);
});
</script>
</html>
`;
}
Finally, the actual server
// server.js
let http = require('http');
let fs = require('fs');
let renderPage = fs.readFileSync('render.html');
let hydratePage = fs.readFileSync('hydrate.html');
let componentsSource = fs.readFileSync('components.js');
http.createServer((req, res) => {
if (req.url == '/components.js') {
// artificial delay
setTimeout(() => {
res.setHeader('Content-Type','text/javascript');
res.end(componentsSource);
}, 2000);
} else if (req.url == '/render.html') {
res.end(renderPage);
} else {
res.end(hydratePage);
}
}).listen(80,'127.0.0.1');
Hydrate is basically used in case of SSR(Server side Rendering). SSR gives you the skeleton or HTML markup which is being shipped from a server so that for the first time when your page loads it is not blank and search engine bots can index it for SEO(A use case of SSR). So hydrate adds the JS to your page or a node to which SSR is applied. So that your page responds to the events performed by the user.
Render is used for rendering the component on client side browser Plus if you try to replace the hydrate with render you will get a warning that render is deprecated and can't be used in case of SSR. it was removed because of it being slow as compared to hydrate.
In addition to above...
ReactDOM.hydrate() is same as render(), but it is used to hydrate(attach event listeners) a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Using ReactDOM.render() to hydrate a server-rendered container is deprecated because of slowness and will be removed in React 17 so use hydrate() instead.
The entire process of putting functionality back into the HTML that was already rendered in server side React is called hydration.
So the process of re-rendering over the once rendered HTML is referred to as hydration.
So if we try to hydrate our application by calling ReactDOM.render() its supposed to be done by calling ReactDOM.hydrate().
render will flush out anything in the specified element(named as 'root' in most cases) and rebuild it ,while hydrate will keep anything that is already inside the specified element and build from that,making the initial page load faster.

setState cause Invariant Violation addComponentAsRefTo

I'm using react-rails with Fluxxor and React. My component is fully functional as long as I stay on the same page.
However, if I change the page by clicking on an other link and come back to my component, when I try to use setState on it, It throw the error :
Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
My actual code can be found here. The problem seems to be the setState method here. Maybe the refs attribut of my Chosen component can't be re-render ? May it be caused by Turbolink ?
I think it's a problem in the implementation of the Chosen component.
When it's given new props, React re-renders it and puts new nodes in the page. However, $.fn.chosen has already been instantiated, and it's attached to DOM nodes that aren't in the page anymore. I suspect that, somewhere along the way, references to old nodes and components are being preserved, even after they're unmounted.
I had the same problem using select2 with React. I found Ryan Florence's guide to jQuery + React to be very helpful:
We need a way to stop rendering with React, do the jQuery dialog work, and then start rendering with React again. Some people call these "Portals". You open a portal for React to skip over a bit of old-school DOM stuff, and then keep going on the other side.
The big trick is rendering nothing and then calling React.renderComponent inside a component.
var Dialog = React.createClass({
render: function() {
// don't render anything, this is where we open the portal
return <div/>;
},
componentDidMount: function() {
var node = this.getDOMNode();
// do the old-school stuff
var dialog = $(node).dialog().data('ui-dialog');
// start a new React render tree with our node and the children
// passed in from above, this is the other side of the portal.
React.renderComponent(<div>{this.props.children}</div>, node):
}
});
source https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md

Categories