TypeError: libraries is undefined in recursive component

I’m trying to achieve a recursive menuItem. I was able to make a basic example with the following code:

/**
* Navigation Component
*
* It renders the navigation links
*/
const Nav = ({ state }) => {
  const { items } = state.source.get("menus/2");

  return (
    <NavContainer>
      {items.map(item => {
        if (item.parent === 0) {  //if it's a top level menuItem
          return <NavItem key={item.id} items={items} item={item} />;
        }
      })}
    </NavContainer>
  );
};

const NavItem = ({ items, item }) => {
  const childItems = items.filter(({ parent }) => parent === item.id);
  const name = item.title.rendered;
  return (
    <div key={item.id}>
      {name}
      {childItems.length > 0 &&  //If it has childrens do it recursive
        childItems.map(childItem => {
          const childChildItems = items.filter(
            ({ parent }) => parent === childItem.id,
          );
          return (
            <NavItem key={childItem.id} items={items} item={childItem} />
          );
        })}
    </div>
  );
};

export default connect(Nav);

However, I was not able to use libraries and state recursively.
My code, what’s not working:
I moved the NavItem to a separate file, so my nav.js looks like:

//nav.js
import React from "react";
import { connect, styled } from "frontity";
import NavItem from "./nav-item";

/**
* Navigation Component
*
* It renders the navigation links
*/
const Nav = ({ state }) => {
  const { items } = state.source.get("menus/2");

  return (
    <NavContainer>
      {items.map(item => {
        if (item.parent === 0) {
          return <NavItem key={item.id} items={items} item={item} />;
        }
      })}
    </NavContainer>
  );
};

export default connect(Nav);

const NavContainer = styled.nav` ...`;

And my nav-item.js:

//nav-item.js
import React from "react";
import { connect } from "frontity";
import Link from "./link";

const NavItem = ({ state, libraries, items, item }) => {
  const name = item.title.rendered;
  const link = libraries.source.normalize(item.url); //without this line the code is working
  const childItems = items.filter(({ parent }) => parent === item.id);
  return (
    <div key={item.id}>
      {name}
      {childItems.length > 0 &&
        childItems.map(childItem => {
          const childChildItems = items.filter(
            ({ parent }) => parent === childItem.id
          );
          return <NavItem key={childItem.id} items={items} item={childItem} />;
        })}
    </div>
  );
};

export default connect(NavItem);

The top-level NavItems does see state and libraries but the childItems are throwing the following error:

The above error occurred in the <NavItem> component:
    in NavItem
    in div
    in Unknown
    in nav (created by Context.Consumer)
    in NavContainer
    in Unknown
    in Unknown
    in div (created by Context.Consumer)
    in HeadContainer
    in Unknown (created by App)
    in HelmetProvider (created by App)
    in App

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries. 3 backend.js:6:2315
    r backend.js:6
    React 9
    batch scheduler.js:10
    batchedUpdates$1 React
    batch scheduler.js:10
    batched scheduler.js:25
    React 3
    unstable_runWithPriority scheduler.development.js:697
    React 5
    unstable_runWithPriority scheduler.development.js:697
    React 4
    batch scheduler.js:10
    batched scheduler.js:25
    (Async: promise callback)
    apply scheduler.js:42
    asyncGeneratorStep index.tsx:10
    _next index.tsx:10
    _asyncToGenerator index.tsx:10
    _asyncToGenerator index.tsx:10
    default index.tsx:7
    <anonymous> client.ts:37
    <anonymous> client.ts:17
    hotApply frontity.module.js:664
    cb process-update.js:76
    check process-update.js:91
    batch scheduler.js:10
    batchedUpdates$1 React
    batch scheduler.js:10
    batched scheduler.js:25
    (Async: promise callback)
    apply scheduler.js:42
    check process-update.js:90
    exports process-update.js:52
    processMessage client.js:279
    handleMessage client.js:139
    handleMessage client.js:102
    (Async: EventHandlerNonNull)
    init client.js:91
    EventSourceWrapper client.js:80
    getEventSourceWrapper client.js:126
    connect client.js:132
    <anonymous> client.js:33
    <anonymous> client.js:12
    js frontity.module.js:2478
    __webpack_require__ frontity.module.js:770
    fn frontity.module.js:130
    0 frontity.module.js:2702
    __webpack_require__ frontity.module.js:770
    <anonymous> frontity.module.js:908
    <anonymous> frontity.module.js:911
TypeError: libraries is undefined
3 frontity.3b4107709437b24ece61.hot-update.js line 11 > eval:8:97
    NavItem nav-item.js:7
    React 4
    batch scheduler.js:10
    batchedUpdates$1 React
    batch scheduler.js:10
    batched scheduler.js:25
    React 6
    performSyncWorkOnRoot self-hosted:920
    flushSyncCallbackQueueImpl React
    unstable_runWithPriority scheduler.development.js:697
    React 4
    batch scheduler.js:10
    batched scheduler.js:25
    (Async: promise callback)
    apply self-hosted:1875
    apply scheduler.js:42
    asyncGeneratorStep index.tsx:10
    _next index.tsx:10
    _asyncToGenerator index.tsx:10
    _asyncToGenerator index.tsx:10
    default index.tsx:7
    <anonymous> client.ts:37
    <anonymous> client.ts:17
    <anonymous> self-hosted:876
    hotApply frontity.module.js:664
    cb process-update.js:76
    check process-update.js:91
    batch scheduler.js:10
    batchedUpdates$1 React
    batch scheduler.js:10
    batched scheduler.js:25
    (Async: promise callback)
    apply self-hosted:1875
    apply scheduler.js:42
    check process-update.js:90
    exports process-update.js:52
    processMessage client.js:279
    handleMessage client.js:139
    handleMessage client.js:102
    (Async: EventHandlerNonNull)
    init client.js:91
    EventSourceWrapper client.js:80
    getEventSourceWrapper client.js:126
    connect client.js:132
    <anonymous> client.js:33
    <anonymous> client.js:12
    js frontity.module.js:2478
    __webpack_require__ frontity.module.js:770
    fn frontity.module.js:130
    0 frontity.module.js:2702
    __webpack_require__ frontity.module.js:770
    <anonymous> frontity.module.js:908
    <anonymous> frontity.module.js:911
TypeError: libraries is undefined
frontity.3b4107709437b24ece61.hot-update.js line 11 > eval:8:97

Edit: I also tried to connect the NavItem when I create it recursievely like:

return connect(
        <NavItem key={childItem.id} items={items} item={childItem} />
      );

Hey @koli14, I think I know what’s happening.

In your first try, you were using NavItem inside NavItem recursively, but connect only at the moment of exporting it, so the NavItem const referenced to a non-connected component. Those rendered recursively were using the NavtItem const and therefore were not connected to the Frontity’s state.

In the second try, you connected a rendered component. You have to use connect with the function that defines the component, not in the React code.

To fix this, you can use connect over the NavItem function; that should work.

import React from "react";
import { connect } from "frontity";
import Link from "./link";

// Use connect here, wrapping the function.
// This way, `NavItem` const references to a connected component,
// and can be used recursively.
const NavItem = connect(({ state, libraries, items, item }) => {
  const name = item.title.rendered;
  const link = libraries.source.normalize(item.url); //without this line the code is working
  const childItems = items.filter(({ parent }) => parent === item.id);
  return (
    <div key={item.id}>
      {name}
      {childItems.length > 0 &&
        childItems.map(childItem => {
          const childChildItems = items.filter(
            ({ parent }) => parent === childItem.id
          );
          return <NavItem key={childItem.id} items={items} item={childItem} />;
        })}
    </div>
  );
});

export default NavItem;

Hell, YEAH! Thanks! :slight_smile:

3 Likes