Testing a React component that modifies an external element - reactjs

I have a React component that, when clicked, updates the style of an HTML element that lives outside of it. Here's what that method looks like:
_hideContent() {
let content = document.getElementById("container");
content.style.top = 100vh;
...
}
When I try to test it using TestUtils.simulate.click(), I get an error due to the external HTML element not being found. The error is:
TypeError: Cannot read property 'style' of null
at FilterButtonMobile._hideContent (public/js/FilterButtonMobile/components/FilterButtonMobile.react.jsx:45:14)
...
How can I test this component?

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" />);

React Component getting added in Vaadin 7 only on button click

I have a vaadin7 application to which I am trying to integrate a sample react application. It is working properly when I implement the React component creation inside a button click. But it is not working if I put the code outside the button click. It shows error "Uncaught TypeError: Cannot read property 'appendChild' of null
at Module.11 (index.js:9)
at u (bootstrap:84)
at t (bootstrap:45)
at Array.r [as push] (bootstrap:32)
at main.90a85973.chunk.js:1" while inspecting.
In vaadin application, i am creating div named "root" and in the index.js of sample react application, I am trying to get the root div created from vaadin and appending "react-root" to "root" div so as to fit the react application in my vaadin layout.
Please find below code for vaadin ui
CustomLayout layout = null;
try {
String dynamicHtml = "<div id=\"root\"></div>";
layout = new CustomLayout(new ByteArrayInputStream(dynamicHtml.getBytes()));
System.out.println("JS Layout");
} catch (IOException e) {
System.out.println("could not create custom layaout"+ e);
}
uimainLayout.addComponent(layout);
//If the below 4 lines of code are added inside the button click, the reactcomponent is rendered inside the layout properly on clicking the button . Otherwise it is showing the "Uncaught TypeError: Cannot read property 'appendChild' of null"
VerticalLayout jsmainLayout = new VerticalLayout();
ReactComponent reactComponent=new ReactComponent();
jsmainLayout.addComponent(reactComponent);
uimainLayout.addComponent(jsmainLayout);
getMainLayout().addComponent(uimainLayout);
setCompositionRoot(getMainLayout());
My Reactcomponent
#JavaScript({"<ip:port>/static/js/2.1f3e22a9.chunk.js","<ip:port>/static/js/3.583f7bad.chunk.js","<ip:port>/static/js/runtime-main.23689a58.js","<ip:port>/static/js/main.90a85973.chunk.js","ReactComponentConnector.js"})
#StyleSheet("<ip:port>/static/css/main.a617e044.chunk.css")
public class ReactComponent extends AbstractJavaScriptComponent {
public ReactComponent() {
System.out.println("Inside ReactComponent");
}
#Override
protected ReactComponentState getState() {
return (ReactComponentState) super.getState();
}
}
ReactComponentState
public class ReactComponentState extends JavaScriptComponentState {
private static final long serialVersionUID = -3283446856435450054L;
}
ReactComponentConnector.js
window.com_p1_p2_ReactComponent = function () {
}
Index.js of my sample react application
const rootEl = document.createElement('div')
rootEl.setAttribute('id', 'react-root');
//error points to the below line
const parentEl =document.getElementById('root');
parentEl.appendChild(rootEl)
ReactDOM.render(
<App />
,
rootEl
);
reportWebVitals();
It's not clear exactly what it is that triggers running the code listed for index.js, but it would seem like it's run before the contents of the custom layout has been initialized. In that way, no element with the id root exists and this would cause the reported error.
I assume creation happens through new ReactComponent(this.getElement()) from inside the JS connector initializer. If this is the case, then they reason for the problem is that both the actions (set custom layout content and initialize the JS connector) are sent to the browser in the same batch, but the connector is initialized in an earlier phase. If the JS component is sent in a batch triggered though a click listener, then this won't be a problem.
To solve this, you could use a direct element reference instead of relying on getElementById in combination with an id defined in the content of a custom layout. The easiest way would be if you'd restructure to use the this.getElement() instance that you're now passing to the ReactComponent constructor.

how to know the component rendering condition in react js

I have one component which is rendering as child as well or user can navigate to that component directly from menu of UI. I am facing one issue now is, if that component as child then its working fine but when I click upon menu to navigate directly then my getDerivedStateFromProps() function is throwing an error as
TypeError: Cannot read property 'length' of undefined
Below is my code. It will work fine if I send a data from parent component to this component and render it from parent component.
static getDerivedStateFromProps(props, state) {
if (props.data.length)// this line is throwing an error when I directly come to this component from menu {
const data = props.data;
return { custData : data };
}
return null;
}
How can I check the condition that not to execute above logic if I am coming to this component directly.
You can double-check like this :
if (props.data && props.data.length)
Check to see if the data exists
if (props?.data)

Applying react Component on html div while keeping the appearance of div as it is

I am a noob and I am trying to use a react component to attach some listeners to an html div section.
Using the component, I just want to attach listeners to the html div to listen to some events while keeping the appearance and formatting of html the same.
- Is it possible to return html's div from render() of the component?
something like this:
class MyComponent extends React.Component {
...
...
document.getElementById("div_id").addEventListener("drop", this._onDrop.bind(this));
render() { return document.getElementById("div_id"); }
(Above code does not work but I am mentioning it just to give an example of what I am trying to do)
- Is there any other way to apply just the listeners to the html div?
.
What I have tried so far:
Above code gives following error:
Uncaught Error: Objects are not valid as a React child (found: [object HTMLDivElement]). If you meant to render a collection of children, use an array instead.
When I return null in render(), it clears the html section.
render() {return false;}
or
render() {return null;}

Strict Null Check Typescript Error on a JSX Element

I am creating a React component with Typescript which has a div JSX element with a 'style' attribute. One of the values is background color, and I'm pulling that value from a Mobx State Tree, like this:
style={{backgroundColor: css.primaryColor}}
However, this creates an error that the value might be undefined.
Normally when calling from the Mobx tree in TS, I set the path equal to a variable, then add an if statement to satisfy this null check, like this:
const { store } = this.props
const currentBot = store.bots.current
if (currentBot) {
do something
}
So in the render function, I tried creating a variable called css, from which I can reference the different keys on the object (primaryColor in the above example). This didn't work, because css could still be undefined, so I also tried adding an OR operator with a default hexcode.
const { store } = this.props
const currentBot = store.bots.current
let css
if (currentBot) {
css = currentBot.theme.css
}
...
<div
style={{backgroundColor: css.primaryColor || '#707070'}}
/>
I'm still getting 'Object is possibly undefined' on the 'css' in the style attribute in VSCode.
How can I satisfy that null check? Do I need to put the entire return statement inside of an if statement?
If the intention is to just remove the error, add ! after the variable which you know is not null to quell this error.This tells the Typescript that the object is not null.
e.g.
<div
style={{backgroundColor: css!.primaryColor || '#707070'}}
/>
If you wish to learn how to use inline styles with Typescript, see the following links-
https://medium.com/#zvona/react-native-and-typescript-meets-styles-b727ecf7e677
https://blog.blueberry.io/how-we-handle-inline-styles-with-typescript-and-react-2c257e039f2b

Resources