I am using nuxt 3 and Compositions API.
I get such a nesting, how to get rid of the extra svg tag?
I would also like to receive svg attributes whenever possible and change, for example, fill
template
<template>
<div>
<component :is="render"></component>
</div>
</template>
Script
import { h } from "vue";
const { data, pending, error, refresh } = await useFetch(svgURL);
const getDataVal = data.value
const SvgToRaw = await getDataVal.text();
const render = () => {
return h("svg", {
class: "bar",
innerHTML: SvgToRaw,
});
};
Chrome Dev Tools
I tried to create a virtual DOM tree and get an HTML element from there, not text, but I think this is a bad solution
import hv from "virtual-dom/h";
import diff from "virtual-dom/diff";
import patch from "virtual-dom/patch";
import createElement from "virtual-dom/create-element";
const betaRender = hv("span", { innerHTML: svgString });
var rootNode = createElement(betaRender);
var patches = diff(rootNode);
return patches[0].vNode.innerHTML
SSR support is important to me so I can't use standard tools
Related
let's consider a list of modules imported arbitrarily as so :
/**
* modules is a list of absolute paths to modules exporting react components
*/
const getAllComponents = async(modules) => {
const components = [];
modules.forEach((moduleName) => {
try {
const module = await import(moduleName);
components.push(module.default);
}catch(err) {
console.warn(err.message)
}
})
return components;
}
and a parent react component in project and a random component exported from a disk based module:
// my-component.js
function MyComponent({moduleNames}) {
const [components, setComponents] = useState([]);
useEffect(() => getAllComponents.then(setComponents), []);
// rendering a random component assuming it exists
const RenderedComponent = components[0];
return (
<div>
{/* failling here: */}
<RenderedComponent />
</div>
)
}
// a-random-component.js (disk based module whose path is in moduleNames in above component props)
function RandomComponent() {
return (<div>propless component</div>)
}
I get the following error when compiling:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of `MyComponent`.
Is there a way to render arbitrary component in the dom without having to use the static import statement ?
I'm using Next.js with styled-jsx, testing enzyme + jest.
I want to test props style but I don't know How can I test.
index.js
const App = (props) => {
const { className, styles } = styles(props);
return (
<div className={`${className}`}>
<h1>test</h1>
{styles}
</div>
)
}
style.js
import css from 'styled-jsx/css';
export default (props) => css.resolve`
h1 {
color: ${props.color} || "red";
}
`
I tried to test this way but It's not working.
const wrapper = shallow(<App color={"blue"}/>);
expect(wrapper.find('h1').prop('style')).toHaveProperty('color', 'blue');
Is there way to solve this problem?
Using react-test-renderer npm package you can do snapshot testing for Style component. Try this, might help you:
import renderer from 'react-test-renderer';
test('Style component unit testing', () => {
const tree = renderer.create(<App color={"blue"} />).toJSON()
expect(tree).toMatchSnapshot()
expect(tree).toHaveStyleRule('color', 'red')
})
It seems that if you don't inject Material-UI stylesheets into a jest/react-testing-library test then jsdom will fail to get the correct styles from your components (e.g. running getComputedStyle(component) will return the incorrect styles for the component).
How you properly setup a jest/react-testing-library test so that the styles are correctly injected into the test? I've already wrapped the components in a theme provider, which works fine.
As a workaround reinserting the whole head (or the element where JSS styles are injected) before assertion seems to apply styles correctly with both getComputedStyle() and react testing library's toHaveStyle():
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS/>
</div>
);
const button = getByTestId("wrapper").firstChild;
document.head.innerHTML = document.head.innerHTML;
expect(button).toHaveStyle(`border-radius: 4px;`);
});
This will still fail though when you're using dynamic styles, like:
myButton: {
padding: props => props.spacing,
...
}
That's because JSS uses CSSStyleSheet.insertRule method to inject these styles, and it won't appear as a style node in the head. One solution to this issue is to hook into the browser's insertRule method and add incoming rules to the head as style tags. To extract all this into a function:
function mockStyleInjection() {
const defaultInsertRule = window.CSSStyleSheet.prototype.insertRule;
window.CSSStyleSheet.prototype.insertRule = function (rule, index) {
const styleElement = document.createElement("style");
const textNode = document.createTextNode(rule);
styleElement.appendChild(textNode);
document.head.appendChild(styleElement);
return defaultInsertRule.bind(this)(rule, index);
};
// cleanup function, which reinserts the head and cleans up method overwrite
return function applyJSSRules() {
window.CSSStyleSheet.prototype.insertRule = defaultInsertRule;
document.head.innerHTML = document.head.innerHTML;
};
}
Example usage of this function in our previous test:
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const applyJSSRules = mockStyleInjection();
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS spacing="8px"/>
</div>
);
const button = getByTestId("wrapper").firstChild;
applyJSSRules();
expect(button).toHaveStyle("border-radius: 4px;");
expect(button).toHaveStyle("padding: 8px;");
});
This ultimately seems like an issue with JSS and various browser implementations like jsdom and and Blink (at least in Chrome). You can see it in Chrome when trying to modify/enable/disable these style rules (you can't).
The behavior appears to be a result of the JSS library using the CSSOM insertRule API. There's a stylesheet generated in the DOM for the styles we expect in our component, but the tag is empty - it's just used to link the shadow CSS back to the DOM. The styles are never written to the inline stylesheet in the DOM, and as a result, the getComputedStyle method does not return the expected results.
There's an open issue to address this behavior and make development easier.
I switched my custom components to styled-components, which does not have some of these idiosyncrasies.
Material-UI is planning on transitioning soon as well.
You could add this to a custom render function. After rendering, the function pulls the styles out of cssom and puts them into a style tag. Here is an implementation:
let customRender = (ui, options) => {
let renderResult = render(ui, options);
let styleElement = document.createElement("style");
let styleText = "";
for (let styleSheet of document.styleSheets) {
for (let rule of styleSheet.cssRules) {
styleText += rule.cssText + "\n";
}
}
styleElement.textContent = styleText.slice(0, -1);
document.head.appendChild(styleElement);
// remove old style elements
let emptyStyleElements = document.head.querySelectorAll('style[data-jss=""]');
for (let element of emptyStyleElements) {
element.remove();
}
return renderResult;
}
I can't speak specifically to Material-UI stylesheets, but you can inject a stylesheet into rendered component:
import {render} from '#testing-library/react';
import fs from 'fs';
import path from 'path';
const stylesheetFile = fs.reactFileSync(path.resolve(__dirname, '../path-to-stylesheet'), 'utf-8');
const styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.innerHTML = stylesheetFile;
const rendered = render(<MyComponent>);
rendered.append(style);
You don't necessarily have to read from a file, you can use whatever text you want.
I'm trying to use https://material-ui.com/ components inside shadow dom, and need a way to inject those styles inside shadow dom. by default material-ui, which uses jss under the hood injects styles in the head of the page.
Is that even possible? Can anyone come with an example?
This is what my web component looks like, it is a web component that renders a react app that contains material-ui styles.
import * as React from 'react';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '#material-ui/styles';
import { create } from 'jss';
import { App } from '#myApp/core';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('span');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot
});
render(
<StylesProvider jss={jss}>
<App />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-web-commponent', MyWebComponent);
Setting the insertionPoint on jss to the actual react root inside the shadow root will tell jss to insert those styles inside that shadow root.
Using https://github.com/Wildhoney/ReactShadow to create shadow dom (you could also do it by hand as shown in previous answer), I created a small WrapperComponent that encapsulates the logic.
import root from 'react-shadow';
import {jssPreset, StylesProvider} from "#material-ui/styles";
import {create} from 'jss';
import React, {useState} from "react"
const WrappedJssComponent = ({children}) => {
const [jss, setJss] = useState(null);
function setRefAndCreateJss(headRef) {
if (headRef && !jss) {
const createdJssWithRef = create({...jssPreset(), insertionPoint: headRef})
setJss(createdJssWithRef)
}
}
return (
<root.div>
<head>
<style ref={setRefAndCreateJss}></style>
</head>
{jss &&
<StylesProvider jss={jss}>
{children}
</StylesProvider>
}
</root.div>
)
}
export default WrappedJssComponent
Then you just need to Wrap your app, or the part of your app you want to shadow inside <WrappedJssComponenent><YourComponent></YourComponent></WrappedJssComponenent>.
Be careful, some of the material-UI component won't work as usual (I had some trouble with
ClickAwayListener, maybe because it uses the parent dom, did not investigate more than that to be honest.
Popper, and everything that will try to use document.body as container will not have access to jss defined in shadow node. You should give an element inside the shadow dom as container.
There is also a whole page in the docs now (MaterialUI 5) that covers how to integrate MUI with a shadow-dom. You also might have to set Portal defaults not to target the dom. https://mui.com/material-ui/guides/shadow-dom/
When using #material-ui/core/CssBaseline with MUI, also emotion styles are being used. In order to support both legacy jss and emotion you can extend the accepted answer above with a CacheProvider like this:
import ReactDOM from 'react-dom/client'
import App from './App'
import createCache from '#emotion/cache'
import { CacheProvider } from '#emotion/react';
import { StylesProvider, jssPreset } from '#material-ui/styles';
import { create } from 'jss';
class ReportComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('div');
const emotionPoint = this.shadowRoot!.appendChild(document.createElement('div'));
const emotionCache = createCache({
key: 'report-component',
container: emotionPoint
});
const reactRoot = this.shadowRoot!.appendChild(mountPoint);
const root = ReactDOM.createRoot(reactRoot);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot
});
root.render(
<StylesProvider jss={jss}>
<CacheProvider value={emotionCache}>
<App />
</CacheProvider>
</StylesProvider>
);
}
}
customElements.define('report-component', ReportComponent);
I'm still a beginner in reactJS (using nodeJS backend) and I have to create a website to manage my collections. I don't know if what I'm going to ask you is feasible, but it probably is.
So I'm using a react component, react-photo-gallery. It's a component where you can use url links and it mixes them together to create a beautiful gallery.
https://github.com/neptunian/react-photo-gallery
I'm using nodeJS to get the information from the database, where I get the urls of all the pictures. For example I have a collection of cards, and an url of the image which represents the collection. What I want to do is get the link of the picture that I'm clicking on so I can use it in another component.
import React from 'react';
import { render } from 'react-dom';
import Gallery from 'react-photo-gallery';
import Photo from './Photo';
class PhotoGallery extends React.Component {
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
urlImages: []
};
}
async componentDidMount() {
var getUrlImages = 'http://localhost:3004';
const response = await fetch(getUrlImages+"/getUrlImages");
const newList = await response.json();
this.setState(previousState => ({
...previousState,
urlImages: newList,
}));
}
galleryPhotos() {
if(this.state.urlImages) {
return this.state.urlImages.map(function(urlimage) {
return { src: urlimage.urlimage, width: 2, height: 2 }
})
}
}
onClick() {
alert(this.galleryPhotos().value);
}
render() {
return (
<Gallery axis={"xy"} photos={this.galleryPhotos()} onClick={this.onClick}/>
)
}
}
const photos = [];
export default PhotoGallery;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Basically what I want to do is get the source link of the picture in the onClick function. Is that possible?
Thanks in advance!
Check the onClick event.
onClick(event) {
alert(event.target.src)
}
The DEMO
The onClick event of the Gallery component has a number of arguments:
the event
an object containing the selected index and the original photo object
You can use this in your onClick handler:
onClick(e, obj) {
const src = obj.photo.src
// do whatever you need with the src (setState, etc)
}