Tenatious
Tenatious

Reputation: 889

Hook Not Updating the DOM

So I have an issue where I am using a hook to set the breadcrumbs of a page, that is relying on the route name initially to set the current breadcrumb. This works fine. However, there are cases where I need to override the breadcrumb with a dynamic value, say a Tag name and for some reason this isn't causing the DOM to update.

This is the Hook:

import { useEffect, useState } from 'react';

import { useLocation, useMatches } from 'react-router-dom';

import { Page } from '@/components/Navigation/Breadcrumbs/Breadcrumbs';

let override: string | undefined = '';

export const overrideBreadcrumb = (title: string | undefined) => {
    override = title;
};

export default function useBreadcrumbs() {
    const matches = useMatches();
    const location = useLocation();
    const [breadcrumbs, setBreadcrumbs] = useState(Array<Page>);

    useEffect(() => {
        let newBreadcrumbs: Array<Page> = [];

        const cachedBreadcrumbs = newBreadcrumbs.map((route) => route.href);

        if (cachedBreadcrumbs.includes(location.pathname)) {
            const currentCrumbIndex = cachedBreadcrumbs.indexOf(location.pathname);

            newBreadcrumbs = newBreadcrumbs.filter((_, index) => index <= currentCrumbIndex);

            setBreadcrumbs(newBreadcrumbs);

            return;
        }

        for (const match of matches) {
            const handle = match.handle as { crumb: string };

            if (match.handle && !cachedBreadcrumbs.includes(handle.crumb)) {
                newBreadcrumbs = [
                    ...newBreadcrumbs,
                    {
                        id: match.id,
                        name: handle.crumb,
                        href: match.pathname,
                        current: match.pathname === location.pathname,
                    },
                ];
            }
        }

        setBreadcrumbs(newBreadcrumbs);

        if (override && breadcrumbs.length && breadcrumbs[breadcrumbs.length - 1].href === location.pathname) {
            const overriddenBreadcrumbs = [...breadcrumbs];
            overriddenBreadcrumbs[overriddenBreadcrumbs.length - 1].name = override;
            setBreadcrumbs(overriddenBreadcrumbs);
        }

    }, [location, matches, override]);

    return {
        breadcrumbs: breadcrumbs,
    };
}

I have created the override variable and set it to '' and then export the overrideBreadcrumb function that I am calling within a page useEffect().

Example of calling the function:

useEffect(() => {
        const currentTag = tagsData?.find((tag) => tag.id === location.pathname.split('/tags/')[1]);

        if (currentTag?.label) {
            console.log(currentTag.label);
            overrideBreadcrumb(currentTag.label);
        }

        return () => {
            overrideBreadcrumb('');
        };
    }, [location.pathname, tagsData]);

I can see in the console.log that the correct label is there, but it never updates the DOM to the value.

In the hook, if I do some logs I can see that the useBreadcrumb useEffect is called correctly but in the return value, this never updates to the new breadcrumb value and always sends the old one.

The main hook is being called as follows:

const { breadcrumbs } = useBreadcrumbs(); <Breadcrumbs pages={breadcrumbs} />

What's the reason for why my code isn't causing the DOM to re-render?

I am expecting the breadcrumb to re-render if the overrideBreadcrumb() function is called within a page useEffect() and that data is different the initial '' that is set for the override variable.

Upvotes: 2

Views: 57

Answers (1)

Youssouf Oumar
Youssouf Oumar

Reputation: 46141

After the initial display, the only thing that causes a component (or a custom hook) to re-render in React (so your useEffect gets called again) is when there is a state change somewhere (when a setState has been called). As they say on the doc:

There are two reasons for a component to render: 1. It’s the component’s initial render. 2. The component’s (or one of its ancestors’) state has been updated.

If you want to change override down the road and have a DOM update, make it a state, something like so:

//...

export default function useBreadcrumbs() {
  const matches = useMatches();
  const location = useLocation();
  const [breadcrumbs, setBreadcrumbs] = useState(Array<Page>);
  const [override, setOverride] = useState("");

  useEffect(() => {
    // ...
  }, [location, matches, override]);

  return {
    breadcrumbs: breadcrumbs,
    setOverride,
  };
}

You use the state setter in place of your current overrideBreadcrumb:

const {breadcrumbs, setOverride} = useBreadcrumbs();
//...
setOverride(currentTag.label);

Upvotes: 2

Related Questions