Displaying LIT web components programatically in React - reactjs

I have the a simple lit web component that renders a input box
import {html} from "lit-element";
import {customElement} from 'lit/decorators.js';
import "./plain-text";
export class MyInputText {
constructor(args){
this.args = args;
}
createPreview(){
const {
colorScheme,
disabled,
labelText,
value,
size,
customizeStyles
} = this.args? this.args: {};
return html`
<plain-text
color-scheme="${colorScheme}"
?disabled="${disabled}"
label-text="${labelText}"
value="${value}"
size="${size}"
customizeStyles="${customizeStyles}"></plain-text>
`;
}
propertyChanged(propertyName, propertyValue){
this.args = {...this.args, [propertyName]:propertyValue}
return this.createPreview();
}
};
I am trying to load it programmatically in react using the following code:
let el = new PlainTextPreviewUI.PlainTextPreview();
el.propertyChanged("width", "300px");
return el;
It returns the following:
With the result above, how can display this web component correctly in React?
I tried rendering it but it gives the following error:
Uncaught Error: Objects are not valid as a React child (found: object with keys {strings, values, type, processor}). If you meant to render a collection of children, use an array instead.
TIA.

React has some support for custom elements already so you can just use the custom element tag name within JSX as long as the custom element definition is loaded and registered.
If you don't need to worry about events, doing something like below is fine.
import './path/to/plain-text.js';
const ReactComponent = () => {
const plainTextProps = {
labelText: 'Text',
disabled: false,
customizeStyles: "",
value:"d"
}
return (
<plain-text {...plainTextProps}></plain-text>
);
}
However, if you need to pass in complex data props or want to declaratively add event listeners, considering using #lit-labs/react for a React component wrapper for the Lit component.

Related

I am trying to dynamically create a Mui icon using React.createElement. However, React lowercases the element. Is there a way to keep case?

I am getting service.icon from JSON, so it looks like so
[
{
"icon": "AdminPanelSettingsIcon",
}
]
I am using React.createElement() like so,
data.map((service) => {
let icon = React.createElement(
service.icon,
{ key: service.icon },
null);
});
my output is <adminpanelsettingsicon></adminpanelsettingsicon>
is there anyway to keep case, or convert to PascalCase so it renders like so <AdminPanelSettingsIcon></AdminPanelSettingsIcon>
Note: Is there anyway to dynamically display mui icons based on specified JSON aka api call.
The docs specify:
React.createElement(
type,
[props],
[...children]
)
The type argument can be either a tag name string (such as 'div' or
'span'), a React component type (a class or a function), or a React
fragment type.
You're attempting to create an element using the string "AdminPanelSettingsIcon". Instead you should be attempting this by passing in the class:
React.createElement(AdminPanelSettingsIcon)
You can do this by converting your list to use classes as the value:
[
{
// The value is a class now, not a string
"icon": AdminPanelSettingsIcon,
}
]
Update:
If you are using https://www.npmjs.com/package/#material-ui/icons (or any other library), you can dynamically import all of the components like this:
//Preparing, it should run inside async function
let maps = {}
let listIcon = [];
await import('#material-ui/icons').then(res => {
listIcon = res
})
for(let icon of listIcon){
maps[icon.constructor.name.toLowerCase()] = icon;
}
//Render
data.map((service) => {
const component = maps[service.icon] || DefaultComponent;
let icon = React.createElement(
component,
{ key: component },
null);
});
Original answer:
If the API output is predictable (service.icon is just a component of the components you defined)
You can do this:
//Import...
//Defined maps
const maps = {
"adminpanelsettingsicon": AdminPanelSettingsIcon,
"adminpanelsettingsicon2": AdminPanelSettingsIcon2,
"adminpanelsettingsicon3": AdminPanelSettingsIcon3,
}
//Render
data.map((service) => {
const component = maps[service.icon] || DefaultComponent;
let icon = React.createElement(
component,
{ key: component },
null);
});
It'll work as you wish
Simple way to do this,
In your .jsx file :
import Icon from '#mui/material/Icon';
export default function MyComponent() {
const iconName = `home`
return (<Icon>{iconName}</Icon>);
}
As a prerequisite, you must include this line in yout index.html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
VoilĂ  !
Sources :
Mui DOC

How can I make a native app that will pick and design charts according to the name fetched from API?

I want my TabDashboardDetails.js to find out which chart to be displayed according to the name of the chart fetched from API. In TabDashboardDetails.js I want to replace CogniAreaChart with a component that will have specific view for fetched chart and can also take data from API.
Here is my TabDashboardDetails.js
import React from 'react';
import DefaultScrollView from '../components/default/DefaultScrollView';
import ChartView from '../components/default/ChartView';
import CogniAreaChart from '../components/CogniAreaChart';
import { mapNameToChart } from '../utils/commonFunctions';
import { areaChartData } from '../chartData';
const TabDashboardDetail = ({ navigation, route }) => {
const tabsConfig = route.params.tabsConfig;
const ChartToDispay = mapNameToChart();
return (
<DefaultScrollView>
{tabsConfig.components.map((comp) => {
console.log(tabsConfig.components);
return (
<ChartView key={comp.name} title={comp.name}>
<CogniAreaChart
name={comp.name}
areaChartData={areaChartData}
height={200}
/>
</ChartView>
);
})}
</DefaultScrollView>
);
};
export default TabDashboardDetail;
I want to pick charts from commonfunctions.js that I have used:
/* eslint-disable prettier/prettier */
import {
AreaChart,
BarChart,
LineChart,
PieChart,
SingleCircularProgress,
Histogram,
SimpleTable,
BubbleChart,
CandlestickChart,
SankeyChart,
ScatterPlot,
StackedBarChart,
WaterfallChart,
TreeMap,
MixAndMatch,
SimpleCard,
BlogTable,
LiquidTable,
} from 'react-native-svg-charts';
export const mapNameToChart = (name) => {
const nameToChart = {
AreaChart: AreaChart,
BarGraph: BarChart,
LineChart: LineChart,
PieChart: PieChart,
SingleCircularProgress: SingleCircularProgress,
Histogram: Histogram,
SimpleTable: SimpleTable,
BubbleChart: BubbleChart,
CandlestickChart: CandlestickChart,
SankeyChart: SankeyChart,
ScatterPlot: ScatterPlot,
StackedBarGraph: StackedBarChart,
WaterfallTable: WaterfallChart,
TreeMap: TreeMap,
MixAndMatch: MixAndMatch,
SimpleCard: SimpleCard,
BlogCard: BlogTable,
LiquidGauge: LiquidTable,
};
return nameToChart[name];
};
You first need to import all the chart types in the file containing mapNameToChart and map the name to the Chart type accordingly. Then You can try this
const ChartToDispay = mapNameToChart(name);
<ChartToDisplay {...your_props_here} />
In your mapNameToChart function it looks like AreaChart is an actual component and the rest are just string names of components instead of the components themselves. You want to change it so that all of the entries in the map are the components. You want mapNameToChart(name) to return a callable component. Then you can call that component with your props.
I'm not fully understanding your the API comes into play here, but it seems like we get the props by looking up the name? I don't know the the API data comes from, so I'm expecting the components array to be passed as a prop to the CustomChart.
const CustomChart = ({name, components, ...props}) => {
// get the component function/class from your map
const Component = mapNameToChart(chart);
// find the component configuration from your API
const config = components.find(obj => obj.name === name);
// call with props from the config and passed down props
return (
<Component
{...config}
{...props}
/>
)
}

Destructure React component into "building blocks"

I am using IonSlides in my app but due to a bug with them, dynamically adding slides can prove difficult.
Because IonSlides is built upon SwiperJS, it has some methods to add and remove slides. The downside to those is that they take a string with HTML in it. In my case, I need to be able to pass in JSX elements so that I can use event listeners on them. Originally, this was my code:
private bindEvents(el: JSX.Element): void {
if (el.props.children) { //Checking if the element actually has children
const children = this.toArray(el.props.children); //If it has only 1 child, it is an object, so this just converts it to an array
children.forEach((c: any) => {
if (!c.props) return; //Ignore if it has no props
const propNames = this.toArray(Object.keys(c.props)); //Get the key names of the props of the child
const el = $(`.${c.props.className}`); //Find the element in the DOM using the class name of the child
propNames.forEach(p => { //Binds the actuall events to the child.
if (Events[p] !== undefined) {
el.on(Events[p], c.props[p]); //`c.props[p]` is the handler part of the event
}
});
});
}
}
Which was called through:
appendSlide(slides: JSX.Element | JSX.Element[]): void {
if (this.slideRef.current === null) return;
this.slideRef.current.getSwiper().then(sw => {
slides = this.toArray(slides);
slides.forEach(s => {
sw.appendSlide(ReactDOMServer.renderToString(s));
this.bindEvents(s);
});
});
}
This worked perfectly when appendSlide was called with an IonSlide:
x.appendSlide(<IonSlide>
<div onClick={() => console.log("Clicked!")}</div>Click me!</IonSlide>
If you clicked the div, it would print "Clicked!".
However, if you pass in a custom component, it breaks. That is because the custom component does not show the children under props. Take this component:
interface Props {
test: string,
}
const TestSlide: React.FC<Props> = (props) => {
return (
<IonSlide>
<div>
{props.string}
</div>
</IonSlide>
);
}
If you were to print that component's props, you get:
props: {test: "..."}
rather than being able to access the children of the component, like I did in the bindEvents function.
There's two ways that I could do fix this. One is getting the JS object representation of the component, like this (I remember doing this ages ago by accident, but I can't remember how I got it):
{
type: 'IonSlide',
props: {
children: [{
type: 'div',
props: {
children: ["..."],
},
}
},
}
or, a slight compromise, destructuring the custom component into its "building blocks". In terms of TestSlide that would be destructuring it into the IonSlide component.
I been trying out things for a few hours but I haven't done anything successful. I would really appreciate some help on this.
For whatever reason that someone needs this, I found you can do el.type(el.props) where el is a JSX element.
This creates an instance of the element so under children instead of seeing the props, you can see the actual child components of the component.

React High Order Components

Lets says I have an array of objects where each object have structure like below
obj = {
name:"Name1",
description:"Description1",
activeState:1,
rating:5
}
const User = (obj) => {
let userActiveState = (obj.activeState === 1) ? 'Active' : 'Non Active';
return (
<tr>
<td>{obj.name}</td>
<td>{obj.description}</td>
<td>{userActiveState}</td>
</tr>
);
}
User.propTypes = {
name: PropTypes.string
description: PropTypes.string
activeState: PropTypes.number
}
User.defaultProps = {
name: "Not Available"
description:""
activeState: 0
}
I use this array to create UI using User(a stateless functional react component), but before the stateless functional react component spits out the UI I want to make some modification to the object properties which are required by the
UI (example using text instead of number for activeState) and not all the object properties are required too.
Where would I remove unwanted properties so that I can have defaultProps and proptypes defined only for the required properties and would I use a high order component which transforms and filters the obj properties?
You don't need a HoC here - a simple composition is enough:
const Component = ...;
const ComponentButDifferentFormats = ({propToSkip, propToRename, ...props}) => (
<Component
{...props}
propRenamed={propToRename}
propX={parseFloat(props.propX)}
propY={'' + props.propY}
/>
);
With this approach, you'll decouple the transformation logic from the real UI. It's really useful for example with an API response. Creating a HoC is also an option: it might be parametrized with formats, filters or even the component itself. One more function in the above example: Component => ....

React.js: How to get all props component expects?

I'm starting to unit test a React.js application and one of the challenge I'm facing is to determine all the props a component needs to mount it properly. Is there any utility to check what all is needed to mount the component successfully? Additionally the data type of those props to initialize them appropriately for component rendering.
Like one of my component is getting props from parent using spread {...props} operator. And the parent is also getting these using spread operator and then adds some additional props and passes it to child. Which makes it very difficult for me to get all props a components expects. Is there any legit way to get the list of props?
An Interesting task.
I start with:
import React from "react";
import * as PropTypes from "prop-types";
function Hello(props) {
const { boo, foo } = props;
return (
<div>
<h1>{boo}</h1>
<h2>{foo}</h2>
</div>
);
}
Hello.propTypes = {
boo: PropTypes.string,
foo: PropTypes.number
};
export default Hello;
I found this article https://blog.jim-nielsen.com/2020/proptypes-outside-of-react-in-template-literal-components/ with function:
/**
* Anytime you want to check prop types, wrap in this
* #param {function} Component
* #param {Object} propTypes
* #return {string} result of calling the component
*/
function withPropTypeChecks(Component) {
return props => {
if (Component.propTypes) {
Object.keys(props).forEach(key => {
PropTypes.checkPropTypes(
Component.propTypes,
props,
key,
Component.name
);
});
}
return Component(props);
};
}
Then I wrote another one:
const getPropsInfo = (component) => {
const result = {};
const mock = Object.keys(component.propTypes).reduce(
(acc, p) => ({ ...acc, [p]: Symbol() }),
{}
);
const catching = (arg) => {
const [, , prop, type] = `${arg}`.match(
/Warning: Failed (.*) type: Invalid .* `(.*)` of type `symbol` supplied to.*, expected `(.*)`./
);
result[prop] = type;
};
const oldConsoleError = console.error.bind(console.error);
console.error = (...arg) => catching(arg);
withPropTypeChecks(component)(mock);
console.error = oldConsoleError;
return result;
};
I chose Symbol as the less expected type.
And called it:
const propsInfo = getPropsInfo(Hello);
console.log(propsInfo);
As result I got: {boo: "string", foo: "number"}
P.S.: I have not tested this on other types. Just for fun! :)
For unit testing this will be totally OK for your parent component to check only that properties which it adds to its child. Because in unit testing you just test functionality of a particular unit. In this case you want to check that your parent component adds all the needed properties to its child and passes all the other properties that it takes (whatever they are).
In parallel you test your child component and check its functionality.
To check that two or more components interact with each other correctly you should use E2E/functional testing. In this case you will test some functionality parts of your working app. If you have some issues with component interaction they will pop up.
One way to solve this problem will be to use decorators. I understand that they are not yet fully here and as for now you might need to use typescript or such. But it is a solution to the stated problem that will allow you to decorate and attach any required information to the properties of your component. Below is an example in typescript:
#PropContainer("Textbox")
export class TextboxProps
{
#Prop(PropertyType.Mandatory)
public propA: string;
#Prop(PropertyType.Optional)
public propB?: number;
}

Resources