How to use custom global declaration in NextJS 14

250 Views Asked by At

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)
3

There are 3 best solutions below

0
Develophir On BEST ANSWER

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.

1
habby On

maybe this can help:

  1. Ensure tsconfig.json is being used by the ts compiler.
  2. After the build, check the js files to ensure that stripHtml exists in the String.prototype
  3. The import order, make sure that utils.d.ts file is being imported before any other file that uses custom declarations.
  4. the most basic, but the one that i always forgot doing: have you restarted the nextjs development server?

if the problem persist, could you share the webpack/ babel configs?

0
Rashid On

Just make sure that utils.d.ts included in the build output and imported before using stripHtml in your component.

You can also consider alternative approaches:

Utility Function: If global modification isn't essential, create a utility function instead of extending String.prototype:

function stripHtml(str: string): string {
  return str.replace(/(<([^>]+)>)/gi, '');
}

Custom Hook: For reusable logic, create a custom hook:

import { useEffect } from 'react';

function useStripHtml() {
  useEffect(() => {
    String.prototype.stripHtml = function () {
      return this.replace(/(<([^>]+)>)/gi, '');
    };
  }, []);
}