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

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

Related

React createProtal called outsite a JSX component not updating the DOM

I am trying to render a dynamically generated react component in a react app using createProtal.
When I call createProtal from a class the component is not rendered.
Handler.ts the class the contains the business logic
export class Handler {
private element: HTMLElement | null;
constructor(selector: string) {
this.element = document.getElementById(selector);
}
attachedEvent() {
this.element?.addEventListener("mouseenter", () => {
let cancel = setTimeout(() => {
if (this.element != null)
this.attachUi(this.element)
}, 1000)
this.element?.addEventListener('mouseleave', () => {
clearTimeout(cancel)
})
})
}
attachUi(domNode: HTMLElement) {
createPortal(createElement(
'h1',
{className: 'greeting'},
'Hello'
), domNode);
}
}
Main.tsx the react component that uses Handler.ts
const handler = new Handler("test_comp");
export default function Main() {
useEffect(() => {
// #ts-ignore
handler.useAddEventListeners();
});
return (
<>
<div id="test_comp">
<p>Detect Mouse</p>
</div>
</>
)
}
However when I repleace attachUi function with the function below it works
attachUi(domNode: HTMLElement) {
const root = createRoot(domNode);
root.render(createElement(
'h1',
{className: 'greeting'},
'Hello'
));
}
What am I missing?
React uses something called Virtual DOM. Only components that are included in that VDOM are displayed to the screen. A component returns something that React understands and includes to the VDOM.
createPortal(...) returns exactly the same as <SomeComponent ... />
So if you just do: const something = <SomeComponent /> and you don't use that variable anywhere, you can not display it. The same is with createPortal. const something = createPortal(...). Just use that variable somewhere if you want to display it. Add it to VDOM, let some of your components return it.
Your structure is
App
-children
-grand children
-children2
And your portal is somewhere else, that is not attached to that VDOM. You have to include it there, if you want to be displayed.
In your next example using root.render you create new VDOM. It is separated from your main one. This is why it is displayed

Displaying LIT web components programatically in React

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.

Use constant value as typescript key name

I am trying to deine and configure all of my screens for react-navigation in a single file a bit like this:
export const SCREEN_1: ScreenConfig = { path: '/screen-1', component: ... };
export const SCREEN_2: ScreenConfig = { path: '/screen-2', component: ... };
This then allows me to render and navigate to the screens like this:
import * as screens from './screens';
screens.map(screen => <Screen name={screen.path} component={screen.component} />);
...
import { SCREEN_1 } from './screens'
navigation.navigate(SCREEN_1.path, props);
I now also wanna add type checking for the props passed to the navigate function. react-navigation allows me to define types like this:
type ScreenParams = {
'/screen-1': Screen1Props,
'/screen-2': Screen2Props,
}
Is there a way to automatically generate the ScreenParams type withouth having to define the path in a second location.
The best I got so far is something like this:
type ScreenParams = {
'/screen-1': React.ComponentProps<SCREEN_1.component>,
'/screen-2': React.ComponentProps<SCREEN_2.component>,
}

Dynamic import and component rendering by string name

I have a data set for some simple actions. Each action has a corresponding color and Material-UI icon. There are about 1,100 available icons. The data looks like this:
{ "items":
[{ "action": "Run",
"icon": "DirectionsRunIcon",
"color":"Red"},
{ "action": "Jump",
"icon": "ArrowUpwardIcon",
"color":"Blue"},
{ "action": "Walk",
"icon": "DirectionsWalkIcon",
"color":"Green"},
]}
Each of the icons requires importing a separate library like so:
import ArrowUpwardIcon from '#material-ui/icons/ArrowUpward'
I could be using any number of icons from #material-ui/icons/ and they're obviously stored as a string in the data. So the question is, how can I conditionally render the icons and only load what I need?
Currently I have a function that just does a huge switch, which won't work long term.
function getIcon(action) {
switch (action) {
case "Run":
return <DirectionsRunIcon />;
case "Jump":
return <ArrowUpwardIcon />;
...
Thanks!
Using something like #loadable/component, you could try to load dynamicly the icon with
import loadable from '#loadable/component';
const Icon = ({name, ...rest}) => {
const ImportedIcon = loadable(() =>
import(`#material-ui/icons/${name}`),
);
return <ImportedIcon {...rest} />;
};
Since you have already have the reference name of the Icon in your datset, you may use the function below:
import { find } from 'lodash';
const getIcon = (action) => {
const requiredObj = find(items, { action }) || {};
return requiredObj.icon;
};
Now, you can use the resulting name to do a dynamic import.

React conditional tag name not working - all letters downsized

I am trying to conditionally render tag name based on prop value.
Example
const SimpleTagName = `Ball${size === 'large' ? 'Large' : 'Small'}`;
return (<SimpleTagName />
but the problem is that I get rendered 'balllarge' tag with all lower case letters. What I am doing wrong ?
Try with this method:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
Official doc ref to handle this pattern: https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime
JSX gets converted to a React.createElement() call, so what you're doing effectively turns into:
React.createElement('balllarge')
Which is not what you want. You need to pass it an component instead of a string, but you can still determine that dynamically, like so:
import { BallLarge, BallSmall } from './Balls' // or whatever
const Component = ({ size }) => {
const BallComponent = size === 'large' ? BallLarge : BallSmall
return <BallComponent />
}
(If you have more than two options, you may need a different way to handle the mapping between your props and variable types, but the principle remains the same: assign a component to a variable, and then use when rendering.)

Resources