Darren Cooney
Darren Cooney

Reputation: 1060

How to Manage a Navigation Menu in NextJS with WordPress

I'm building a NextJS app using headless WordPress with GraphQL. It's not clear from the documentation where I should be calling the query to create the site navigation. https://github.com/lfades/next.js/tree/examples/cms-wordpress/examples/cms-wordpress

The navigation is controlled dynamically by WordPress Menus (Appearance > Menus) on the backend and I can successfully access these menuItems via GraphQL without any issue on the index.js and posts/[slug].js page templates in Next JS.

// index.js
export default function Index({ primaryMenu = [] }) {
   return (
     <Layout>
         <Header> 
          {primaryMenu.map((item, index) => {
             return (<a href={item.url}>{item.label}</a>)
          )}
         </Header>
     </Layout>
   );

}

export async function getStaticProps() {
  const primaryMenu = await getPrimaryMenu(); // Get menu via GraphQL
  return {
    props: { primaryMenu },
  };
}

The issue I'm having with this is I am repeating the getStaticProps function on each template and I should be able to use some sort of global query for this, either in the <header/> component itself or another method. I'm unable to find documentation on how to do this and it doesn't work in components.

Any guidance (or examples) on where a global query such as a dynamic Navigation query would live in a NextJS app is appreciated.

Upvotes: 6

Views: 4350

Answers (2)

hyperdrive
hyperdrive

Reputation: 1846

I battled this for a while (for JD site) with redux and wp rest, but I think theory should be the same for gql + apollo client.

You need to override Next App _app with a custom class that extends App.

And you might need to inject an instance of apollo client into AppContext using a HOC. I used this wrapper for Redux. Would need to be modelled after that.

Edit: (Looks like someone has made it already)

// export default withRedux(makeStore)(MyApp);
export default withApollo(apolloClient)(MyApp); ???

Then in your App getInitialProps, you can make query to get menu. By default apollo client query will grab cached value if it's in the cache store already I believe.

  static async getInitialProps(appContext) {
    const { isServer,  pathname, apollo? } = appContext.ctx;

    // do menu query
    const menu = apollo.query???

    // Redux version
    // const state = store.getState();
    // let main_menu = state.menu;
    // if (!state.menu) {
    //  const menu = await apiService().getMenu("main");
    //  main_menu = menu;
    //  store.dispatch({ type: "SET_MENU", payload: menu });
    // }

    ...

    // call the page's `getInitialProps` and fills `appProps.pageProps`
    const initialProps = await App.getInitialProps(appContext);

    const appProps: any = {
      ...initialProps,
      menu: main_menu
    };

    return appProps;

  }

Now menu is in the page props of the App Component, which can be passed down.
Or you can use apollo client to make the query again in a child component. So when you make the query again, in header or whatever, it will take the cached response provided it's the same query.

I made an endpoint for menus that included the template name + post slug along with the menu items and mapped the wp templates to next routes.

const menu = useSelector((state: any) => state.menu);
const menuItems = menu.map((item: any) => {
    const path = getTemplatePath(item.template);
    return (
      <Link key={item.slug} href={`/${path}`} as={`/${item.slug}`} scroll={false}>
        <a>{item.title}</a>
      </Link>
    );
  });

Upvotes: 0

Imran Sayed
Imran Sayed

Reputation: 76

There are a couple of ways you can do it:

  1. You can menuItems query with useQuery() from @apollo/client inside the Layout component so that its available to all pages which are wrapped inside the Layout. However the problem with this is that, there will be a load time and the data won't be prefetched and readily available like we can do with getServerSideProps() ( at page level ). Because this will be at component level.

    import { useQuery } from "@apollo/client";

    export default function Layout () {

    const { loading, data } = useQuery( GET_MENU_QUERY ) return {...} }

  2. You can use swr that uses caching strategy. There is blog that explains how to use it

Upvotes: 1

Related Questions