Rendering stenciljs stateless components - stenciljs

I've been using StencilJS for some time now,
and coming from React background, my immediate instinct for writing some components is to write them stateless.
However, the stencil documentation doesn't mention the stateless components at all.
That's why I am writing here to learn other people experience with it

You should look at functional components: https://stenciljs.com/docs/functional-components to create stateless components, and they:
aren't compiled into web components,
don't create a DOM node,
don't have a Shadow DOM or scoped styles,
don't have lifecycle hooks,
According to the doc, if a component has to hold state, deal with events, etc, it should probably be a class component. If a component's purpose is to simply encapsulate some markup so it can be reused across your app, it can probably be a functional component

You can write functional components inside Stencil elements. As an example:
#Component({
tag: 'my-app',
styleUrl: 'my-app.css',
shadow: true
})
export class MyApp {
render() {
return (
<div>
<Loading />
</div>
);
}
}
const Loading = () => {
return (
<div class="loading">
<h1>Activating Santa</h1>
<span>🎅🎄🎁</span>
</div>
);
};
In this case <Loading> is a stateless functional component similar to React's model - you can obtain its props and get children, etc.
Stateless Stencil components can not be exported as top-level Web Components - those must be defined as classes.

Related

Vue 3 replacing the HTML tags where v-html is called with the provided HTML

This is about a Vue 3 app with Vite, not webpack.
For now, as you can see from this issue on vite's issue page, vite doesn't have a convenient way of inlining SVGs without using external plugins. Vite does however, support importing files as raw text strings. As such, I had an idea to use this feature and to inline SVG's by passing the raw SVG strings into an element's v-html.
It actually works great, the SVG shows up on the page as expected and I can do the usual CSS transforms (the whole purpose of inlining them like this), but it's not perfect. As it currently stands, the element that receives the v-html directive simply places the provided HTML nested as a child. For example, if I do <span v-html="svgRaw" />, the final HTML comes out something like this
<span>
<svg>
<!-- SVG attributes go here -->
</svg>
</span>
Is there any way for me to essentially replace the parent element on which v-html is declared with the top-level element being passed to it? In the above example, it would mean the <span> just becomes an <svg>
EDIT:
Thanks to tony19 for mentioning custom directives.
My final result looks like this:
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("inline", (element) => {
element.replaceWith(...element.children);
});
app.mount("#app");
Then, in the component I simply use the directive, <svg v-html="svgRaw" v-inline /> and it works great!
You could create a custom directive that replaces the wrapper element with its contents:
Use app.directive() to create a global directive, named v-inline-svg:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App)
.directive('inline-svg', el => {
if (!el) {
return
}
// copy attributes to first child
const content = el.tagName === 'TEMPLATE' ? el.content : el
if (content.children.length === 1) {
;[...el.attributes].forEach((attr) => content.firstChild.setAttribute(attr.name, attr.value))
}
// replace element with content
if (el.tagName === 'TEMPLATE') {
el.replaceWith(el.content)
} else {
el.replaceWith(...el.children)
}
})
.mount('#app')
In your component, include v-inline-svg on the v-html wrapper element (also works on <template> in Vue 3):
<svg v-html="svgRaw" v-inline-svg />
<!-- OR -->
<template v-html="svgRaw" v-inline-svg />
demo
I found that using the method above works but is only good for a single rendering of the svg... The element starts throwing errors if I try to change the svg contents dynamically, not sure why but assuming that the dom replacement has something to do with it.
I modified the code slightly for my use case.
app.directive('inline-svg', {
updated: (element) => {
if (element.children.length === 0) {
return
}
const svg = element.children[0]
if(svg.tagName.toLowerCase() !== 'svg') {
return
}
for (let i = 0; i < svg.attributes.length; i++) {
const attr = svg.attributes.item(i)
element.setAttribute(attr.nodeName, attr.nodeValue)
}
svg.replaceWith(...svg.children)
}
})
In my component I have.
<svg v-if="linkType !== null" v-html="linkType" v-inline-svg></svg>
The directive now copies the svg attributes across from the child to the parent and then replaces the child with it's children.
Coming from Vue2. I think this still works:
Instead of span you can use the special Vue tag template:
<template v-html="svgRaw" />
This will not render <template /> as a tag itself, but render the elements given in v-html without a parent element.

Testing visibility of React component with Tailwind CSS transforms using jest-dom

How can I test whether or not a React component is visible in the DOM when that component is hidden using a CSS transition with transform: scale(0)?
jest-dom has a .toBeVisible() matcher, but this doesn't work because transform: scale(0) is not one of the supported visible/hidden triggers. Per the docs:
An element is visible if all the following conditions are met:
it is present in the document
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
it does not have the hidden attribute
if <details /> it has the open attribute
I am not using the hidden attribute because it interfered with my transition animations. I am using aria-hidden, but that is also not one of the supported triggers.
The simplified version of my component is basically this. I am using Tailwind CSS for the transform and the transition.
import React from "react";
import clsx from "clsx";
const MyComponent = ({isSelected = true, text}) => (
<div
className={clsx(
isSelected ? "transform scale-1" : "transform scale-0",
"transition-all duration-500"
)}
aria-hidden={!isSelected}
>
<span>{text}</span>
</div>
)
I could potentially check for hidden elements with:
toHaveClass("scale-0")
toHaveAttribute("aria-hidden", true)
But unlike toBeVisible, which evaluates the entire parent tree, these matchers only look at the element itself.
If I use getByText from react-testing-library then I am accessing the <span> inside the <div> rather than the <div> which I want to be examining. So this doesn't work:
import React from "react";
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import { MyComponent } from "./MyComponent";
it("is visible when isSelected={true}", () => {
const {getByText} = render(
<MyComponent
isSelected={true}
text="Hello World"
/>
);
expect(getByText("Hello World")).toHaveClass("scale-1");
});
What's the best way to approach this?

Is it possible to globally define links to use a specific component?

I'm currently trying to use Nav with react-router. The default behavior reloads the page, so I'm trying to use the Link component from react-router-dom.
It's quite difficult to preserve the default styling when overriding linkAs.
Is there any global way to override link navigation behavior?
Like defining a global link render function, which I can then set to render the Link component from react-router-dom?
Yes, it's possible!
2 things are required:
Make a wrapper component that translates the Nav API to react-router-dom links.
Specify the linkAs prop to the Nav component.
Wrapper component
This is a simple component that creates a react-router-dom link while using styles from Fabric:
import { Link } from "react-router-dom";
const LinkTo = props => {
return (
<Link to={props.href} className={props.className} style={props.style}>
{props.children}
</Link>
);
};
Specify component for use in Nav
<Nav groups={links} linkAs={LinkTo} />
Have also created a full working example at https://codesandbox.io/s/xenodochial-wozniak-y10tr?file=/src/index.tsx:605-644

Should the main App component be Pure or Stateless?

In React-Native, should we use Pure Component or Stateless Function for the main Component ?
Here is two ways of doing it:
import React from 'react';
import { Provider } from 'react-redux';
import store from './reducers/AppReducers';
import AppRoutes from './routes/AppRoutes';
// Pure Component
class App extends React.PureComponent {
render() {
return (
<Provider store={store}>
<AppRoutes />
</Provider>
);
}
}
// Stateless Function
const App = () => {
return (
<Provider store={store}>
<AppRoutes />
</Provider>
);
};
If your component is simple, use stateless. For simple components it is not required to use Pure Components
Let's say if you have a component that displays a text and you make it a pure component, Everytime it re-renders it will first do the shallow comparison.
In this situation a re-render would be performant then a shallow comparison.
It's upto you to decide weather your component would be performant if it checks for a shallow comparison or re-renders
TIP: If you have a very basic component, which only displays some basic stuff, use stateless.
https://medium.com/groww-engineering/stateless-component-vs-pure-component-d2af88a1200b
here it is explained in detail.
in your case, I would advice Pure Component as it contains your whole app, and a re-render would be more costly then a shallow comparison

Pass prop to parent in Vuetify and Vue JS

How do I pass the dark mode value from navbar (child) to app.vue (parent)?
Within my navbar component I have a switch to enable/disable dark mode. I'd like to pass that dark data up to the parent (app.vue) to change the entire app.
Thank you!
You can use Vue's custom events interface. https://v2.vuejs.org/v2/guide/components-custom-events.html
In your child navbar component you can have a method:
handleThemeChange: function (mode) {
this.$emit('handle-theme-change', { mode });
}
And then in your parent App component watch for that event:
<App v-on:handle-theme-change="handleThemeChange" />
Then your app component can have a method handleThemeChange that actually handles the change. The handleThemeChange method in your app component will accept the object as a parameter.