0

I've created a custom component to simply layout content inside IonCardContent. This works great for my needs so far:

interface ContainerProps {
    position?: string;
    content?: string,
    colour?: string;
    custClass?: string;
}

const CardContainer: React.FC<ContainerProps> = ({ position = "right", content = "n/a", colour = "", custClass = "" }) => {
    
    if ( position.trim().toLowerCase() === "right" ) {
        return <><div className={"ion-float-right " + custClass} >{content}</div><div className="clear-right"></div></>
    } else if ( position.trim().toLowerCase() === "left" ) {
        return <div className={"ion-float-left " + custClass}>{content}</div> 
    } else if ( position.trim().toLowerCase() === "full" ) {
        return <div className={""  + custClass}>{content}</div>
    } else if ( position.trim().toLowerCase() === "empty" ) { 
        return <div className={""  + custClass}>&nbsp;</div> 
    } else {
        return null
    }  
};

export default CardContainer;

However, I'd now like to start passing in some html elements to the component so I can do things like highlight sections of text by making it bold or by wrapping part of the string in a span and adding a css class to it.

At present, adding html to the content attribute just results in this being displayed as a literal string. This is obviously due to "content" being declared as a string.

Is the solution to displaying html simply changing the type declaration in the Interface? If so, what to? Or is a more complex solution required?

Thanks.

Phill Healey
  • 3,084
  • 2
  • 33
  • 67
  • I have no idea about ionic, but can't you just use React's `children` prop to pass them?- https://reactjs.org/docs/composition-vs-inheritance.html#containment – Dilshan Nov 17 '21 at 13:35
  • Does this answer your question? [How to safely render html in react?](https://stackoverflow.com/questions/38663751/how-to-safely-render-html-in-react) –  Nov 17 '21 at 15:41
  • @Dilshan, Thanks for the tip. That's a new concept to me weirdly. I'm not sure if it really works in this context but it does help me deal with another issue I had. – Phill Healey Nov 17 '21 at 15:43
  • @E.Maggini I'm not sure it does. I don't need to sanitise since the "html" is system generated. I just need to be able to display the passed in string as html rather than as a string. So for example pass this in to the component "HELLO" and it be displayed as html rather than displayed as a literal string. Thanks. – Phill Healey Nov 17 '21 at 15:51
  • @E.Maggini I had considered the information, otherwise I wouldn't have been able to say; "I'm not sure it does". According to https://blog.logrocket.com/using-dangerouslysetinnerhtml-in-a-react-application/ you don't need to sanitise when using dangerouslySetInnerHTML. Also I am returning html from the custom component itself without any sanitisation. Hence, my response of; "I'm not sure it does". – Phill Healey Nov 20 '21 at 11:59

2 Answers2

0

From the duplicate answer in comments:

import sanitizeHtml from 'sanitize-html';

const MyComponent = () => {
  dirty = '<a href="my-slug" target="_blank" onClick="evil()">click</a>';
  const clean = sanitizeHtml(dirty, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a'],
    allowedAttributes: {
      a: ['href', 'target']
    }
  });
  return (
    <div 
      dangerouslySetInnerHTML={{__html: clean}}
    />
  );
};

therefore

import sanitizeHtml from 'sanitize-html';

interface ContainerProps {
    position?: string;
    content?: string,
    colour?: string;
    custClass?: string;
}

 const CardContainer: React.FC<ContainerProps> = ({ position = "right", 
 content = "n/a", colour = "", custClass = "" }) => {

const myHTML = null;
    
    if ( position.trim().toLowerCase() === "right" ) {
        myHTML = '<div className={"ion-float-right " + custClass} >{content}</div><div className="clear-right"></div>'
    } else if ( position.trim().toLowerCase() === "left" ) {
        myHTML = '<div className={"ion-float-left " + custClass}>{content}</div>' 
    } else if ( position.trim().toLowerCase() === "full" ) {
        myHTML = '<div className={""  + custClass}>{content}</div>'
    } else if ( position.trim().toLowerCase() === "empty" ) { 
        myHTML = '<div className={""  + custClass}>&nbsp;</div>'
    }

 const clean = sanitizeHtml(myHTML, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a'],
    allowedAttributes: {
      a: ['href', 'target']
    }
  });
  return (
    <div 
      dangerouslySetInnerHTML={{__html: clean}}
    />
  );
};

export default CardContainer;

You will of course need to adjust the allowedTags and allowedAttribute values to suit your needs

Phill Healey
  • 3,084
  • 2
  • 33
  • 67
  • Thanks that's very helpful. I'll give it a try. – Phill Healey Nov 18 '21 at 11:51
  • Unfortunately this is giving me all sorts of issues displaying variables inside the markup strings generated in my conditonal statements. Also, I am confused as to why my if/else strings display without sanitisation, but you said that html has to be sanitised before react can display it. – Phill Healey Nov 19 '21 at 10:40
  • 1
    I've managed to make some changes to get me nearer to the end result. Thanks. – Phill Healey Nov 19 '21 at 15:15
0

Passing all my html output plus dynamic content passed in via variable, into the santizer meant that the variables were either stripped out or their calls were displayed as literal markup.

Therefore, it was necessary to just sanitize the variable that contained the html content passed to the component.

import React from 'react';
import './CardContainer.css';
import sanitizeHtml from 'sanitize-html';

interface ContainerProps {
    position?: string;
    content?: string,
    colour?: string;
    custClass?: string;
}

const CardContainer: React.FC<ContainerProps> = ({ position = "right", content = "n/a", colour = "", custClass = "" }) => {
    
    const clean = sanitizeHtml(content, {
        allowedTags: ['b', 'i', 'em', 'strong', 'a', 'div', 'span'],
        allowedAttributes: {
          a: ['href', 'target'],
          div: ['class', 'className'],
          span: ['style', 'class', 'className']
        }
    });
    


    if ( position.trim().toLowerCase() === "right" ) {
        return <><div className={"ion-float-right " + custClass} dangerouslySetInnerHTML={{__html: clean}}/><div className="clear-right"></div></>
    } else if ( position.trim().toLowerCase() === "left" ) {
        return <div className={"ion-float-left " + custClass} dangerouslySetInnerHTML={{__html: clean}}/>
    } else if ( position.trim().toLowerCase() === "full" ) {
        return <div className={""  + custClass} dangerouslySetInnerHTML={{__html: clean}}/>
    } else if ( position.trim().toLowerCase() === "empty" ) { 
        return <div className={""  + custClass}>&nbsp;</div> 
    } else {
        return null
    }  
};

export default CardContainer;
Phill Healey
  • 3,084
  • 2
  • 33
  • 67