typescriptnext.js.d.ts

How to use custom global declaration in NextJS 14


I want to use custom global declaration in NextJS

I have a NextJS project in which I have created a global prototype to String like below

utils.d.ts

export {}

declare global {
  interface String {
    /**
     * Returns string after removing all html tags.
     */
    stripHtml(): string
  }
}
String.prototype.stripHtml = function (): string {
  return this.replace(/(<([^>]+)>)/gi, '')
}

Then I've included utils.d.ts file in tsconfig.json like below

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "paths": {
      "@/*": ["./*"]
    },
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["utils.d.ts", "next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Then I've used it inside my component Example.tsx like below

Exmaple.tsx

import React from 'react'

interface Props {
  posts: Record<string, any>[]
}

const Example: React.FC<Props> = ({ posts }) => {
  return (
    <section>
      <div className="container">
        <div>
          {posts.map((item, idx) => {
            const post = item.attributes
            const { title, content } = post
            return (
              <div key={`post-${idx}`}>
                <div>
                  <h2>{title}</h2>
                  <p>{content.stripHtml()}</p>
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </section>
  )
}

export default Example

Now VS Code is recognizing the declaration and not showing any red lines like before. Now when I'm running the project using dev command getting below error.

TypeError: content.stripHtml is not a function
    at eval (webpack-internal:///./components/Example.tsx:37:58)
    at Array.map (<anonymous>)
    at Example (webpack-internal:///./components/Example.tsx:21:40)
    at renderWithHooks (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5658:16)
    at renderIndeterminateComponent (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5731:15)
    at renderElement (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5946:7)
    at renderNodeDestructiveImpl (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6104:11)
    at renderNodeDestructive (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
    at renderNode (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
    at renderChildrenArray (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6211:7)
    at renderNodeDestructiveImpl (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6141:7)
    at renderNodeDestructive (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
    at renderNode (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
    at renderChildrenArray (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6211:7)
    at renderNodeDestructiveImpl (/Users/vickyvish/Projects/eweb-next/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6141:7)

Solution

  • You did everything correct in sense of TypeScript, however the problem was that you did not extend the String prototype with the function stripHtml, hence the error: "stripHtml is not a function.".

    The correct solution would be to declare the type in a utils.d.ts file and extend String prototype in a separate file utils.ts, then import it once at the top-level of the app.

    The app's root layout.tsx would be the most optimal place to import it, that way it is accessible in the whole the components tree.

    Example:

    File: utils.ts

    import './utils.d'
    
    String.prototype.stripHtml = function (): string {
        return this.replace(/(<([^>]+)>)/gi, '')
    }
    

    File: utils.d.ts

    export {}
    
    declare global {
        interface String {
            /**
             * Returns string after removing all html tags.
             */
            stripHtml(): string
        }
    }
    

    File: app/layout.tsx

    import '@/utils'
    

    Edit: P.s. This practice is usually called prototype pollution which is a bad practice, for reasons that I won't ramble on here as there are many articles explaining the reasoning of it. One suggestion would be to declare the util function in a module and import it only in files where it's needed.