NiklasMencke / nextjs-breadcrumbs

A dynamic, highly customizable breadcrumbs component for Next.js
https://www.npmjs.com/package/nextjs-breadcrumbs
108 stars 50 forks source link

Add support for Next.js 13 #47

Open karvas opened 1 year ago

karvas commented 1 year ago

Error: Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>. Learn more: https://nextjs.org/docs/messages/invalid-new-link-with-extra-anchor

BWBama85 commented 1 year ago

Would like this as well

Isaac-Tait commented 1 year ago

I am having this error too and it has been driving me mad trying to figure out what was going on until today when I narrowed it down to this plugin... Thanks @karvas for posting 🤪 Temporary work around is that I can delete the breadcrumb "component" from my code so I can run localhost and then re-add it when I push my code to production... 🤷🏻‍♂️

MSAZ89 commented 1 year ago

Please

notaspecial commented 1 year ago

still waiting

JunkyDeLuxe commented 1 year ago

Still waiting, this repository seems not to be maintained anymore ... Just need to add legacyBehaviour into the <Link ...>, or remove the html tag after updating Next version ..

mudiagauwojeya commented 1 year ago

I would have contributed this code but I see the repo has some PR from since 2021 that have not been resolved. Too bad, this would have been have great help for my project.

travistylervii commented 1 year ago

Still broken. :(

AlexAnschuetz commented 1 year ago

In lieu of this issue being resolved, can anyone suggest an alternate approach to breadcrumbs in nextjs13 ?

JunkyDeLuxe commented 1 year ago

I used this lib, but now, I use React bootstrap breadcrumb instead

Le jeu. 20 avr. 2023 à 01:17, Alex Anschuetz @.***> a écrit :

In lieu of this issue being resolved, can anyone suggest an alternate approach to breadcrumbs in nextjs13 ?

— Reply to this email directly, view it on GitHub https://github.com/NiklasMencke/nextjs-breadcrumbs/issues/47#issuecomment-1515499623, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABMVBRS2R7WZOJAWWBUYTQ3XCBXBPANCNFSM6AAAAAARV4IXT4 . You are receiving this because you commented.Message ID: @.***>

marketsystems commented 1 year ago

I have removed all the excess package garbage and outdated build dependency stuff etc.. and made the code work with my nextjs13 project (new router requires different commands) plus next/link wont work with legacy tags inside. I will create new npm package

marketsystems commented 1 year ago

Added version for nextjs13 (using app dir and new next/navigation router) - might need seperate version for using pages dir npm install @marketsystems/nextjs13-appdir-breadcrumbs

(is embedded with "use client" as obviously wont work with router as a server component!)

I deleted so much of original repo I didnt do it as a github fork from original project but comments and issues can be added here: https://github.com/marketsystems/nextjs13-appdir-breadcrumbs

haveamission commented 1 year ago

@marketsystems Is it a drop in replacement?

chozzz commented 1 year ago

Created a PR for this here https://github.com/NiklasMencke/nextjs-breadcrumbs/pull/48


Desperate measure: For now I just installed it from my own github repo yarn add https://github.com/chozzz/nextjs-breadcrumbs which is not ideal.

Henkisch commented 12 months ago

Hey everyone, bumped in to this as well which was very confusing. The package doesn't seem to be maintained anymore, however:

I made a copy of the src/index.tsx (This file) file and put it in my own repo in a component I call: BreadCrumbs.tsx. So now I can control and adapt the code myself and still use the foundation and functionality of this package. If I need to support the former Link behavior, add legacyBehavior to the <Link>s in BreadCrumbs.tsx, if not, remove the <a> tags inside the <Link>s in BreadCrumbs.tsx and you're good to go with the current Next.js version.

If you need App router support, consider using Marketsystem's solution above.

Base breadcrumb component

import React, { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";

/**
 * Takes an URL String and removes query params and hash params
 *
 * @param url - The URL string
 * @returns The transformed URL string
 *
 */
const getPathFromUrl = (url: string): string => {
  return url.split(/[?#]/)[0];
};

/**
 * Takes a breadcrumb title (from url path) and replaces
 * special chars to more readable chars
 *
 * @param title - The breadcrumb title
 * @returns The transformed title or the result of the custom transformLabel function
 *
 */
const convertBreadcrumb = (
  title: string,
  toUpperCase: boolean | undefined,
  replaceCharacterList: Array<CharacterMap> | undefined,
  transformLabel?: ((title: string) => React.ReactNode) | undefined
): React.ReactNode => {
  let transformedTitle = getPathFromUrl(title);

  if (transformLabel) {
    return transformLabel(transformedTitle);
  }

  if (replaceCharacterList) {
    for (let i = 0; i < replaceCharacterList.length; i++) {
      transformedTitle = transformedTitle.replaceAll(
        replaceCharacterList[i].from,
        replaceCharacterList[i].to
      );
    }
  }

  // decode for utf-8 characters and return ascii.
  return toUpperCase
    ? decodeURI(transformedTitle).toUpperCase()
    : decodeURI(transformedTitle);
};

export interface Breadcrumb {
  /** Breadcrumb title. Example: 'blog-entries' */
  breadcrumb: string;

  /** The URL which the breadcrumb points to. Example: 'blog/blog-entries' */
  href: string;
}

export interface CharacterMap {
  /** The source character or character pattern that should be replaced (e.g. 'ae') */
  from: string;

  /** The replacement into which the character should be replaced. */
  to: string;
}

export interface BreadcrumbsProps {
  /** If true, the default styles are used.
   * Make sure to import the CSS in _app.js
   * Example: true Default: false */
  useDefaultStyle?: boolean;

  /** The title for the very first breadcrumb pointing to the root directory. Example: '/' Default: 'HOME' */
  rootLabel?: string | null;

  /** Boolean indicator whether the root label should be omitted. Example: true Default: false */
  omitRootLabel?: boolean;

  /** Boolean indicator if the labels should be displayed as uppercase. Example: true Default: false */
  labelsToUppercase?: boolean | undefined;

  /** Array containing a list of specific characters that should be replaced in the label. This can be useful to convert special characters such as vowels. Example: [{ from: 'ae', to: 'ä' }, { from: '-', to: ' '}] Default: [{ from: '-', to: ' ' }] */
  replaceCharacterList?: Array<CharacterMap> | undefined;

  /** A transformation function that allows to customize the label strings. Receives the label string and has to return a string or React Component */
  transformLabel?: ((title: string) => React.ReactNode) | undefined;

  /** Array containing all the indexes of the path that should be omitted and not be rendered as labels. If we have a path like '/home/category/1' then you might want to pass '[2]' here, which omits the breadcrumb label '1'. Indexes start with 0. Example: [2] Default: undefined */
  omitIndexList?: Array<number> | undefined;

  /** An inline style object for the outer container */
  containerStyle?: any | null;

  /** Classes to be used for the outer container. Won't be used if useDefaultStyle is true */
  containerClassName?: string;

  /** An inline style object for the breadcrumb list */
  listStyle?: any | null;

  /** Classes to be used for the breadcrumb list */
  listClassName?: string;

  /** An inline style object for the inactive breadcrumb list item */
  inactiveItemStyle?: any | null;

  /** Classes to be used for the inactive breadcrumb list item */
  inactiveItemClassName?: string;

  /** An inline style object for the active breadcrumb list item */
  activeItemStyle?: any | null;

  /** Classes to be used for the active breadcrumb list item */
  activeItemClassName?: string;
}

const defaultProps: BreadcrumbsProps = {
  useDefaultStyle: false,
  rootLabel: "Home",
  omitRootLabel: false,
  labelsToUppercase: false,
  replaceCharacterList: [{ from: "-", to: " " }],
  transformLabel: undefined,
  omitIndexList: undefined,
  containerStyle: null,
  containerClassName: "",
  listStyle: null,
  listClassName: "",
  inactiveItemStyle: null,
  inactiveItemClassName: "",
  activeItemStyle: null,
  activeItemClassName: "",
};

/**
 * A functional React component for Next.js that renders a dynamic Breadcrumb navigation
 * based on the current path within the Next.js router navigation.
 *
 * Only works in conjunction with Next.js, since it leverages the Next.js router.
 *
 * By setting useDefaultStyle to true, the default CSS will be used.
 * The component is highly customizable by either custom classes or
 * inline styles, which can be passed as props.
 *
 * @param props - object of type BreadcrumbsProps
 * @returns The breadcrumb React component.
 */
const Breadcrumbs = ({
  useDefaultStyle,
  rootLabel,
  omitRootLabel,
  labelsToUppercase,
  replaceCharacterList,
  transformLabel,
  omitIndexList,
  containerStyle,
  containerClassName,
  listStyle,
  listClassName,
  inactiveItemStyle,
  inactiveItemClassName,
  activeItemStyle,
  activeItemClassName,
}: BreadcrumbsProps) => {
  const router = useRouter();
  const [breadcrumbs, setBreadcrumbs] = useState<Array<Breadcrumb> | null>(
    null
  );

  useEffect(() => {
    if (router) {
      const linkPath = router.asPath.split("/");
      linkPath.shift();

      const pathArray = linkPath.map((path, i) => {
        return {
          breadcrumb: path,
          href: "/" + linkPath.slice(0, i + 1).join("/"),
        };
      });

      setBreadcrumbs(pathArray);
    }
  }, [router]);

  if (!breadcrumbs) {
    return null;
  }

  return (
    <nav
      style={containerStyle}
      className={containerClassName}
      aria-label="breadcrumbs"
    >
      <ol
        style={listStyle}
        className={useDefaultStyle ? "_2jvtI" : listClassName}
      >
        {!omitRootLabel && (
          <li style={inactiveItemStyle} className={inactiveItemClassName}>
            <Link href="/">
              {convertBreadcrumb(
                rootLabel || "Home",
                labelsToUppercase,
                replaceCharacterList,
                transformLabel
              )}
            </Link>
          </li>
        )}
        {breadcrumbs.length >= 1 &&
          breadcrumbs.map((breadcrumb, i) => {
            if (
              !breadcrumb ||
              breadcrumb.breadcrumb.length === 0 ||
              (omitIndexList && omitIndexList.find((value) => value === i))
            ) {
              return;
            }
            return (
              <li
                key={breadcrumb.href}
                className={
                  i === breadcrumbs.length - 1
                    ? activeItemClassName
                    : inactiveItemClassName
                }
                style={
                  i === breadcrumbs.length - 1
                    ? activeItemStyle
                    : inactiveItemStyle
                }
              >
                <Link href={breadcrumb.href}>
                  {convertBreadcrumb(
                    breadcrumb.breadcrumb,
                    labelsToUppercase,
                    replaceCharacterList,
                    transformLabel
                  )}
                </Link>
              </li>
            );
          })}
      </ol>
    </nav>
  );
};

Breadcrumbs.defaultProps = defaultProps;

export default Breadcrumbs;

Then you can then import your own abstraction of a breadcrumb component or use it as before:

Your own breadcrumb component

import React from 'react';
import Breadcrumbs from 'YourBreadCrumbComponent';

const MyBreadCrumb = () => {
  return (
    <Breadcrumbs
      useDefaultStyle={true}
    />
  );
};

export default MyBreadCrumb;