Reputation: 1464
I am using DOMPurify.sanitize()
inside dangerouslySetInnerHTML={{}}
to display innerHtml
returned from the database. For initial purpose I'm using getServersideProps()
with next-redux-wrapper
for this page.
I installed dompurify with: npm i -S dompurify
, present version is: "^2.2.6".
My code:
import DOMPurify from 'dompurify';
import { useSelector } from 'react-redux';
import { END } from 'redux-saga';
import wrapper from '../redux/storeSetup';
const EmployeePage = () => {
const blog = useSelector(state => state.data);
const html_body = blog[0].body_html;
const clean = DOMPurify.sanitize(html_body);
return(
<div className="mainContainer">
<div dangerouslySetInnerHTML ={{ __html: clean }} />
<ul>
{blog.map((item) => (
<li key={item.author}>
<span>{item.author}</span><br/>
<span>{item.title}</span>
<h4>{item.body_delta}</h4>
<p>{item.body_html}</p>
<p>{item.created_on}</p>
</li>
))}
</ul>
</div>
);
}
export const getServerSideProps = wrapper.getServerSideProps( async ({store}) => {
store.dispatch({type: GET_DATA});
store.dispatch(END);
await store.sagaTask.toPromise();
});
export default EmployeePage;
But when I'm running this with npm run dev
I'm getting this error:
TypeError: dompurify__WEBPACK_IMPORTED_MODULE_1___default.a.sanitize is not a function
.
What is wrong here? I tried with even simpler codes but everything shows the same error! What am I doing wrong?
Upvotes: 39
Views: 40347
Reputation: 58
You can use dynamic import for Dompurify used component. Parent component:
const Content = dynamic(() => import('./Content'), {
ssr: false,
loading: () => <p>Loading...</p>
});
component file:
'use client';
import DOMPurify from 'dompurify';
const Content: React.FC = () => {
const example = "<p> hello </p>
return (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(selectedTask?.content || '', { USE_PROFILES: { html: true } })
}}
/>
);
};
export default Content;
Upvotes: 0
Reputation: 2610
Try this:
❌ import * as DOMPurify from 'dompurify';
⭕️ import DOMPurify from 'dompurify';
from: https://github.com/cure53/DOMPurify/issues/526#issuecomment-1435711907
Upvotes: -1
Reputation: 6294
dompurify
just work on client side component, so you need to add this line "use client";
to top of (tsx,jsx) file, see this full example :
"use client";
import React, { useEffect, useState } from "react";
import DOMPurify from 'dompurify';
export default function MarkdownTextView({text}: { text: string }) {
const [sanitizedHtml, setSanitizedHtml] = useState("");
useEffect(() => {
// This code will be executed only on the client side after the component is mounted
if (text.length === 0) return;
const domPurify = DOMPurify(window);
const cleanHtml = domPurify.sanitize(text); // You might want to pass your markdown-converted HTML here
setSanitizedHtml(cleanHtml);
}, [text]); // This effect will re-run if the 'text' prop changes
if (!sanitizedHtml) return null; // or some placeholder/loading indicator
return (
<div dangerouslySetInnerHTML={{__html: sanitizedHtml}}></div>
);
}
Upvotes: 0
Reputation: 4944
Using DOMPurify in Next.js can be accomplished with by installing jsdom
and canvas
to construct a window
object and pass to the DOMPurify()
function.
CodeSandbox Demo (uses app directory): https://codesandbox.io/p/sandbox/lingering-river-j5fpi7?file=%2Fapp%2Fpage.tsx
First, install the libraries:
npm install jsdom canvas
In the app/
directory, you can use React Server Components to run jsdom
on the server:
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
export default function Home() {
return (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify(new JSDOM('<!DOCTYPE html>').window).sanitize(
'<img src=x onerror=alert(1)//>'
),
}}
/>
);
}
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
export default function Home(props) {
return (
<div
dangerouslySetInnerHTML={{
__html: props.sanitizedHtml,
}}
/>
);
}
export function getServerSideProps() {
const sanitizedHtml =
DOMPurify(new JSDOM('<!DOCTYPE html>').window).sanitize(
'<img src=x onerror=alert(1)//>'
);
return {
props: {
sanitizedHtml: sanitizedHtml,
},
};
}
In the pages/
directory, you'll also need to configure webpack to mark jsdom
and canvas
as externals (add the webpack
key to your next.config.js
file):
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
webpack: (config) => {
config.externals = [...config.externals, 'canvas', 'jsdom'];
return config;
},
};
More details here:
https://github.com/vercel/next.js/issues/46893
Upvotes: 1
Reputation: 8692
Inspired from the two other solutions of this thread - isomorphic-dompurify and the one provided on Github.
jest-setup.ts
file (provided in the jest.config
as
setupFilesAfterEnv: ["<rootDir>/jest-setup.ts"]
):const DOMPurify = require("dompurify");
const { JSDOM } = require('jsdom');
const { window } = new JSDOM("<!DOCTYPE html>");
global.DOMPurify = DOMPurify(window);
This will make DOMPurify work with Jest.
window
and global
sides so that it works both in the browser and in jest / NodeJS contexts. e.g. in TypeScriptimport { sanitize as DOMPurifySanitize } from "dompurify";
export const sanitize = (text: string): string => {
const isBrowser = typeof window !== 'undefined';
return isBrowser ? DOMPurifySanitize(text) : global.DOMPurify.sanitize(text);
}
Upvotes: 1
Reputation: 729
I just switch to js-xss and made a component like that :
import React from "react";
import xss from "xss";
const Html = ({ unsafeHtml }: {unsafeHtml: string}): JSX.Element => {
return (
<div
{...props}
dangerouslySetInnerHTML={{
__html: xss(unsafeHtml),
}}
/>
);
};
Upvotes: 8
Reputation: 595
Ran into this yesterday with a TypeScript/React set up.
import DOMPurify from 'dompurify';
export const sanitize = (html: string): string => DOMPurify.sanitize(html);
This code worked perfectly inside Jest tests but failed in the Browser as the DOMPurify object was undefined. Upon digging I found that there was a DOMPurify object attached at the window scope. I wound up having to put a hack inplace to handle the differing behavior between running in node and in the browser:
import DOMPurify from 'dompurify';
interface IDOMPurifyWindow extends Window {
DOMPurify: typeof DOMPurify;
}
const purify =
((window as unknown) as IDOMPurifyWindow)?.DOMPurify || DOMPurify;
export const sanitize = (html: string): string => DOMPurify.sanitize(html);
There is a compiler warning but it's functional.
I could eliminate the compiler warning by importing the es.js version of the library directly: import DOMPurify from 'dompurify/dist/purify.es.js';
But that resulted in Jest being unable to run as it needs vanilla JavaScript.
This library does great things, but it's not super friendly to use if you're using TypeScript and running in a browser.
Upvotes: 1
Reputation: 1464
I found a working solution: instead of sanitizing the innerHtml on the server side, I had to sanitize it on the client side right after submitting the rich-text blog, in my case, react-quill
:
import dynamic from 'next/dynamic'
import {useState} from 'react'
import DOMPurify from 'dompurify';
const QuillNoSSRWrapper = dynamic(import('react-quill'), {
ssr: false,
loading: () => <p>Loading...</p>,
})
// quill modules definitions
//...
export default function articles() {
const [text, setText] = useState(preData);
function handleTextChange(content, delta, source, editor) {
//let str = JSON.stringify(editor.getContents());
//let parsed = JSON.parse(str)
setText(editor.getContents());
const cleaned = DOMPurify.sanitize(editor.getHTML());
console.log('Cleaned Html: ', cleaned);
return (
<div className="quill_container">
<div id="editor" className="editor">
<QuillNoSSRWrapper
id="quilleditor"
modules={modules}
formats={formats}
theme="snow"
value={text}
onChange={handleTextChange}
/>
</div>
</div>
);
};
Upvotes: 1
Reputation: 6346
According to this: https://github.com/cure53/DOMPurify/issues/29#issuecomment-466626162
Try the follow (from the example above):
import { JSDOM } from 'jsdom'
import DOMPurify from 'dompurify'
const { window } = new JSDOM(html_body);
const domPurify = DOMPurify(window);
console.log(domPurify.sanitize(html_body));
Upvotes: 0