Using Material Design Lite with React - reactjs

I am trying to use MDL on an existing project that uses React, and I am running into several issues. Things seem fine on the first load, although there are many warning messages:
Warning: ReactMount: Root element has been removed from its original container. New container:
This happens pretty much for every DOM element that has a class recognized by MDL (examples: mdl-layout, mdl-layout__content, etc.) and it happens on any DOM changes.
Further, when changing routes, there is an "Invariation Violation" error:
Uncaught Error: Invariant Violation: findComponentRoot(..., .0.2.0.1.1.0.0.0.0): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser)...
Does this mean that MDL and React are pretty much incompatible?
Update: The issue disappears if the element with class="mdl-js-layout" is not the topmost element in the reactjs render function. Once I wrapped that element, all is good.

Try to wrap a DIV element outside, I just fixed that problem in this way.
If you are using Redux + React + MDL + React-Router, You can wrap a DIV element to Provider element:
import React, { createStore } from 'react';
import { Provider } from 'react-redux';
import Router, { HistoryLocation } from 'react-router';
var store = createStore();
Router.run(routes, HistoryLocation, (Handler, state) => {
React.render((
<div>
<Provider store={store}>
{
() => <Handler {...state} />
}
</Provider>
</div>
), document.body);
});
Hope it can help you :)

The first and second errors are related to each other, take a look at MDL's layout source code. I would say that the following causes the mutation of your React root element (which is the mdl-js-layout component):
var container = document.createElement('div');
container.classList.add(this.CssClasses_.CONTAINER);
this.element_.parentElement.insertBefore(container, this.element_);
this.element_.parentElement.removeChild(this.element_);
container.appendChild(this.element_);
If you take a look at the official example, you can see that MDL massively changes your markup and that is exactly what React doesn't like.
BTW: I also have composed an article which studies the combination of React with MDL.

Related

Replace DOM with JSX in React 18

How to remove this error message from my console
I'm using ReactDOM.render to replace certain "unreachable" parts of my code with JSX components, it worked fine in previous versions but now I'm getting this annoying error message and I want to get rid of it.
Long story:
I'm using the FullCalendar lib for react18 and Nextjs.
I'm facing a limitation from the lib, in previous versions I was able to pass JSX to render in the header buttons, but in the current version 5.11.2 it's not possible anymore, it only let you set either text or a bootstrap/font-awesome icon.
So I instead used an old known trick to replace DOM with no more than the HTML element
ReactDOM.render(
<AnyIconIWantToUse />,
window.document.querySelector("#element-to-replace-id")
)
and that is what brings up the said error message
What I've tried
As the error suggest I've tried using createRoot instead but it gives me an error too (and afaik it's meant to be used only with the root component so I prefer not to use it).
This should help you out
createPortal(
<AnyIconIWantToUse />,
document.getElementById("element-to-replace-id")
)
I ended up achieving what I wanted with another approach.
Instead of replacing DOM content directly with JSX I instead render the desired JSX into the DOM and replace the DOM with DOM
// utils/replaceDOM.ts
import type React from 'react';
import { renderToString } from 'react-dom/server';
type ReplaceDOM = (
elementToReplace: Element,
replacement: React.ReactElement
) => void;
const replaceDOM: ReplaceDOM = (elementToReplace, replacement) => {
if (!replacement) return;
// Get html from component (only get first render)
const replacementHTML = renderToString(replacement);
// Parse html string into html
const parser = new DOMParser();
const parsedDocument = parser.parseFromString(replacementHTML, 'text/html');
const replacementElement = parsedDocument.body.children[0];
// Append replacement to DOM
window.document.body.prepend(replacementElement);
// Replace children with element
elementToReplace.replaceWith(replacementElement);
};
export default replaceDOM;
Then I can use it as desired
replaceDOM(elementToReplace, <ElementIWant className="w-6" />);

What happens when React rerenders?

I have the following code and you can view it on codeSandbox:
import "./styles.css";
import lottie from "lottie-web";
import { useEffect, useRef } from "react";
import logo from "pic";
export default function App() {
const el = useRef(null);
useEffect(() => {
if (el != null) {
lottie.loadAnimation({
container: el,
renderer: "svg",
loop: true,
autoplay: true,
animationData: logo
});
}
}, []);
return (
<div className="App">
<div ref={el}></div>
</div>
);
}
Lottie is just some library to render animation. The problem I have with it is that while I am in the developing phase, if I make some modification to the second div tag, then React rerenders without destroying the previous animation and in the meanwhile create a new animation below the original one. I am aware that one has to add some effect clearing logic in useEffect but I just do not see why.
I am new to React and only have a very basic understanding of how React works in the background, here is what I think: basically when I change the second div tag, the diff algorithm notices that it has been altered, so it goes ahead and tries to modify the dom associated with it. In my opinion it can 1). either update the current dom 2). delete it and append a new dom. In the first situation, shouldn't it leave the first animation unchanged without adding a new animation? In the second situation, if it gets rid off the current dom, why would the original animation be kept?
React can do both scenarios you mentioned: update existing and destroy & re-create. And React uses its own algorithm to decide when to do which.
You can force React to use destroy & re-create strategy using a special props called key.
Here is an example: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
Quoted from the link above:
When a key changes, React will create a new component instance rather than update the current one
I couldn't run the example you provided via code sandbox and just by reading your code, I can see that your App component has no props and no state. Thus, there is no way for you to update it.
Your code said: when componentDidMount use lottie to do some animation inside the second div. So, chances are the animation issue you're having is probably coming from lottie not React.

reactJS adding an event listener to a navbar tile

I am running the current version of reactJS with hooks. I have three code modules in my app: header.js which creates a navbar and exports it to app.js which adds some other objects and exports all of this to index.js.
I am trying to add an event listener to the individual tiles in the navbar so that I can redirect to the appropriate page.
code
var listenerElement = document.getElementById("Tile1");
if (listenerElement !== null) {
listenerElement.addEventListener("click", navbarClicked) ;
console.log(listenerElement);
} else {
console.log("Element with ID=Tile1 not found");
}
<div id="Tile1" className="linkcontainer">Home</div>
/code
However, I cannot find an appropriate place to add the event-listener and the element with ID "Tile1" is never found - perhaps because it hasn't been rendered as yet?
The element in question is only rendered by index.js but I can't add a function after the reactDOM.render block in index.js - I get an error "not a react function"
Any suggestions would be much appreciated :-)
For react, you should use "refs" to link to a specific aspect of the navabar.
But as you are just trying to have something affect the navbar, you should use the "onClick" property for the div or iconButton or similar.
for example in a function component:
function handleClick() {
console.log("clicked");}
<nav> <iconButton onClick={handleClick} > button </iconButton> </nav>
https://reactjs.org/docs/handling-events.html
In react, you don't work with the DOM directly. React makes a copy of the DOM called virtual DOM and then compares them to update the DOM. You should add your event listener using props.
So instead of:
document.getElementById("Tile1").addEventListener("click", navbarClicked);
you should do <div onClick={navbarClicked} id="Tile1" className="linkcontainer">Home</div>

onsen react navigator pushpage overwrites last page

In a project, my intention is to use only one Navigator element to handle all page navigation.
There is one singleton appState all over the code, and navigator is member of that, it gets initialized by the outermost App component.
ReactDOM.render(
<App/>,
document.getElementById('app')
);
The navigator is initiated by:
initialRoute = {
component: LoginPage,
props: {
}};
and
<Ons.Navigator
initialRoute={this.initialRoute}
renderPage={this.renderPage.bind(this)}
/>
and
renderPage(route: Route, navigator: typeof Ons.Navigator) {
const props: any = route.props || {};
if (appState.navigator == null) {
appState.navigator = navigator;
}
props.navigator = appState.navigator;
return React.createElement(route.component, route.props);
}
correctly with initialRoute. When I call pushPage(newRoute), the newRoute is apparently added, when checked at the time of addition. That is, I get the following right after pushing newRoute:
LoginPage
HomePage
However, a subsequent call to pushPage(someOtherRoute) yields
LoginPage
SomeOtherRouteComponent
I would expect
LoginPage
HomePage
SomeOtherRouteComponent
I have verified that there is no issue with synchronization etc, when I push the route object to an aside list, I get everything without any loss. But just pushPage is not working as I expect.
Any ideas, or missing something obvious? The snippets are TS.
It appears that any error during page load is caught by Navigator, and that causes the page to be not added into routes (but the page is still navigated to).
I have filed an issue on github with a workaround. An alternative workaround is to make sure that there are no JS errors during page load, which may not be 100% the case given 3rd party modules are present.

Error in using react-bootstrap

I'm using React-bootstrap package on my Keystone.JS project. To test I tried putting a button in my index page. However, I get the following warning:
Warning: Unknown props bsStyle, active, block, navItem,
navDropdown, bsClass on tag. Remove these props from the
element.
This is the code where I use Button:
var React = require('react');
var Layout = require('../../layouts/defaultLayout');
var ReactDOMServer = require('react-dom/server');
var Button = require('react-bootstrap').Button;
module.exports = React.createClass({
render: function () {
return (
<Button bsStyle="primary">Default button</Button>
);
}
});
What am I missing? Thanks for the help.
Long story short
Many component libraries (including react-bootstrap see issue) were relying on passing custom properties to DOM elements w/o data- prefix.
Starting 15.2.0 React warns about unknown properties on DOM element
Add warning for unknown properties on DOM elements. (#jimfb in #6800, #gm758 in #7152)
You need to update library. This was fixed in v0.30.0-rc.1.

Resources