Component
Components are the most basic building block of an application. They are composed of templates, logic, and styles, and are used to split the UI into small, reusable pieces of code.
A component file must have the extension .tsx
instead of .ts
since it contains JSX code.
Logic and Template
A component file combines the logic and template in a single file. It is a function that returns a single JSX element with zero to many child JSX elements. To indicate that the file contains JSX code, the component file must have a .tsx
extension instead of .ts
.
Example.
export function Greeting() {
return <h1>Hello World!</h1>
}
Styles
MonsterJS uses Sass
by default, but we can also use other CSS frameworks depending on our webpack configuration. The component styles will only affect its corresponding component and will have no effect on its parent or child components.
Component styles are imported directly to the .tsx
file.
Example.
import styles from './greeting.component.scss';
import { component } from '@monster-js/core';
export function Greeting() {
return <h1>Hello World!</h1>
}
component(Greeting, styles);
In the example above, we use the component
function to attach the style to the component. However, the component function is only necessary if styles are applied to the component. If no styles are applied, the component function can be omitted.
To ensure that component styles work properly, the component's logic and styles should have the same filename with a .component.tsx
extension for the logic and a .component.scss
extension for the styles. Additionally, the styles should be imported directly into the component's .tsx file.
Example.
greeting
├── greeting.component.tsx
└── greeting.component.scss
Render a Component Into View
To render a component into view, we need to import the component and create a JSX element using the imported component, as shown in the following example:
import { App } from './app.component';
<App></App>
Shadow Dom Component
We can also create a Shadow DOM component to encapsulate our component. To do this, we can use a custom shadowComponent
function. This function takes three arguments. The first argument is the function component itself, the second argument is an optional Shadow DOM mode (open
or closed
), which defaults to open if not specified, and the third argument is an optional component style.
Example.
import styles from './greeting.component.scss';
import { shadowComponent } from '@monster-js/core';
export function Greeting() {
return <h1>Hello World</h1>
}
shadowComponent(Greeting, 'closed', styles);
Web Component Slot
Using Shadow DOM, we can pass elements from the parent component to the child component's view by using slots. Slots are placeholder elements in the child component's Shadow DOM that allow content from the parent component to be displayed.
Example.
Parent component
import { Child } from './child.component';
export function Parent(props) {
return <div>
<Child>
<h1>I am a slot content</h1>
<span>I am a slot content</span>
</Child>
</div>
}
All elements inside the <Child></Child>
tag will be displayed in the child component slot.
Child component
import { shadowComponent } from '@monster-js/core';
export function Child() {
return <div>
<slot></slot>
</div>
}
shadowComponent(Child);
Named Slot
Named slots allow us to choose where we want to display the elements inside the child component's view. A component can have multiple named slots.
Example.
Parent component
import { Child } from './child.component';
export function Parent() {
return <div>
<Child>
<h1 slot="slot-1">I am a slot content</h1>
<span slot="slot-2">I am a slot content</span>
</Child>
</div>
}
Child component
import { shadowComponent } from '@monster-js/core';
export function Child() {
return <div>
<slot name="slot-1"></slot>
<div>
<slot name="slot-2"></slot>
</div>
</div>
}
shadowComponent(Child);
In the example above, the element <h1 slot="slot-1">I am a slot content</h1>
from parent component will be displayed in <slot name="slot-1"></slot>
in child component. The same for the <span slot="slot-2">I am a slot content</span>
will be displayed in <slot name="slot-2"></slot>
.
Slots only work when using shadow DOM.
Custom Element Component
Custom elements allow us to define a new type of element. To create a custom element, we can use the customElement
function. This function has three parameters: the first is the function component, the second is the type of custom element constructor, and the third is the type of element it will extend.
Example.
import { customElement } from '@monster-js/core';
export function CustomButton {
return <span>I am a button!</span>
}
customElement(CustomButton, HTMLButtonElement, 'button');
To render the above component within its parent component, use the following code:
<CustomButton />
When the above example component is rendered, it generates an HTML element like this:
<button>
<span>I am a button!</span>
</button>
As long as the custom element is defined we can also use it like the following:
<button is="app-custom-button"></button>
Custom element names are prefixed with app-
when defined and the custom-button
is the name of the component CustomButton
converted to kebab case. It is also good to set the component selector using setComponentSelector
function.
Custom Element Component Styles
If we want to use styles in our component, we can also use the component
function before the customElement
function.
Example.
import styles from './custom-button.component.scss';
import { component, customElement } from '@monster-js/core';
export function CustomButton {
return <span>I am a button!</span>
}
component(CustomButton, styles);
customElement(CustomButton, HTMLButtonElement, 'button');
Component Selector
MonsterJS components are web components. If we inspect the element after rendering the component, we can see that it's added to the DOM with the component name converted to kebab case and prefixed with app-
as the component selector.
Example.
export function CustomButton() {
return <div></div>
}
If we render this component to its parent component, it will look like the following:
<CustomButton />
If we inspect the elements in the DOM tree, we can see that the component is rendered as follows:
<app-custom-button></app-custom-button>
We can also set the component selector using setComponentSelector
.
Example.
import { setComponentSelector } from '@monster-js/core';
export function CustomButton() {
return <div></div>
}
setComponentSelector(CustomButton, 'my-custom-button');
Define Component
In order to use a Component outside of a MonsterJS component, it must first be defined using customElement.define
. This is because MonsterJS components are web components.
Example.
import { createComponent } from '@monster-js/core';
import { Greeting } from './greeting.component';
customElement.define('app-greeting', createComponent(Greeting));
or we can use the defineComponent
function provided by the core package.
Example.
import { defineComponent } from '@monster-js/core';
import { Greeting } from './greeting.component';
defineComponent('app-greeting', Greeting);
Components used within a MonsterJS component do not need to be separately defined, as they are automatically recognized within the scope of the MonsterJS component.
Define Custom Element Component
Custom element components created by customElement
function can also be defined using the customElement.define method.
Example.
import { createComponent } from '@monster-js/core';
import { CustomButton } from './custom-button.component';
customElement.define('app-custom-button', createComponent(CustomButton), {
extends: 'button'
});
or we can use the defineComponent
function provided by the core package.
Example.
import { defineComponent } from '@monster-js/core';
import { CustomButton } from './custom-button.component';
defineComponent('app-custom-button', CustomButton, {
extends: 'button'
});