How to convert a String to a React Node/element

1k Views Asked by At

I'm keeping a menu list in my database, and with it I also keep the icons. The challenge is that the Icons are MUI icons and being saved as the React node element. For example: <HomeIcon fontSize="small" />

When I'm rendering the menu list, everything looks great, BESIDE the icons - as it render them as text and not as React elements.

I tried so many options to render them as React, but couldn't find the right solution.

The last thing I used is Parse ('html-react-parser'), but with no success as it saves the elements in lower cases.

This is the parsing code:

  const icon = (item.icon ? parse(item.icon, {lowerCaseTags: false}) : "");

But then I'm receiving the following error:

Warning: The tag <homeicon> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
    at homeicon
    at span

That's the React node that I'm rendering:

     <DashboardSidebarItem
        active={partialMatch}
        chip={item.chip}
        depth={depth}
        icon={icon}
        info={item.info}
        key={key}
        open={partialMatch}
        path={item.path}
        title={item.title}
      >
        {renderNavItems({
          depth: depth + 1,
          items: item.children,
          path
        })}
      </DashboardSidebarItem>

And that's DashboardSidebarItem element:

export const DashboardSidebarItem = (props) => {
  const {
    active,
    children,
    chip,
    depth,
    icon,
    info,
    open: openProp,
    path,
    title,
    ...other
  } = props;
  const [open, setOpen] = useState(openProp);

  const handleToggle = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  let paddingLeft = 24;

  if (depth > 0) {
    paddingLeft = 32 + 8 * depth;
  }
// I NEED TO CONVERT IT TO REACT NATIVE
  // Branch
  if (children) {
    return (
      <ListItem
        disableGutters
        sx={{
          display: 'block',
          mb: 0.5,
          py: 0,
          px: 2
        }}
        {...other}>
        <Button
          endIcon={!open
            ? <ChevronRightIcon fontSize="small" />
            : <ChevronDownIcon fontSize="small" />}
          disableRipple
          onClick={handleToggle}
          startIcon={icon}
          sx={{
            color: active ? 'secondary.main' : 'neutral.300',
            justifyContent: 'flex-start',
            pl: `${paddingLeft}px`,
            pr: 3,
            textAlign: 'left',
            textTransform: 'none',
            width: '100%',
            '&:hover': {
              backgroundColor: 'rgba(255,255,255, 0.08)'
            },
            '& .MuiButton-startIcon': {
              color: active ? 'secondary.main' : 'neutral.400'
            },
            '& .MuiButton-endIcon': {
              color: 'neutral.400'
            }
          }}
        >
          <Box sx={{ flexGrow: 1 }}>
            {title}
          </Box>
          {info}
        </Button>
        <Collapse
          in={open}
          sx={{ mt: 0.5 }}
        >
          {children}
        </Collapse>
      </ListItem>
    );
  }

  // Leaf
  return (
    <ListItem disableGutters sx={{display: 'flex', mb: 0.5, py: 0, px: 2 }}>
      <NextLink href={path} passHref >
        <Button
          component="a"
          startIcon={icon}
          endIcon={chip}
          disableRipple
          sx={{
            backgroundColor: active && 'rgba(255,255,255, 0.08)',
            borderRadius: 1,
            color: active ? 'secondary.main' : 'neutral.300',
            fontWeight: active && 'fontWeightBold',
            justifyContent: 'flex-start',
            pl: `${paddingLeft}px`,
            pr: 3,
            textAlign: 'left',
            textTransform: 'none',
            width: '100%',
            '& .MuiButton-startIcon': {
              color: active ? 'secondary.main' : 'neutral.400'
            },
            '&:hover': {
              backgroundColor: 'rgba(255,255,255, 0.08)'
            }
          }}
        >
          <Box sx={{ flexGrow: 1 }}>
            {title}
          </Box>
          
          {info}
        </Button>
      </NextLink>
    </ListItem>
  );
};
1

There are 1 best solutions below

8
On

I wouldn't recommend you to do any kind of parsing due security vulnerabilities like XSS.

I could be wrong but if you are loading icons from the database like:

<homeicon/>

Remember that user components must be capitalized, btw, you also need to import the icons before using them, so, a better approach would be:

  • Instead of storing in the database the whole component, just store the icon name.
  • Use the Icon component from the official documentation and just pass the icon name as a children.

Navigate to the following section for more details: Icon (Font icons)

This is a basic example of the whole implementation:

import Icon from '@mui/material/Icon';

const IconList = ({ icons = [] }) => {
 return (
  <>
    {icons.map({name} => <Icon>{name}</Icon>)}
  </>
 );
};

In this way you could also store props like color, sx, fontSize, etc, from the official material-ui documentation and store them in the database to make the component more escalable.