user3725781
user3725781

Reputation: 735

Changing state on route change Next.js

I'm toggling navigation in next.js, it works fine, it's just I want the navigation to close again when the route changes.

For example, if I'm on the home page and toggle the navigation, the navigation opens and shows a link to the about page. If I click that link, I get taken to the about page as expected - but the navigation is still open!

I've tried a few things - I think I want to utilize onRouteChangeComplete(url) but I'm struggling to update the navActive state.

My page.js file:

class Page extends Component {
  state = {
    navActive: false
  };

  toggle = () => {
    this.setState({
      navActive: !this.state.navActive
     });
  };

  render() {
    return (
      <ThemeProvider theme={theme}>
        <StyledPage className="full-page-wrapper">
          <Meta />
          <Header navActive={this.state.navActive} toggle={this.toggle} />
          <PrimaryContent>{this.props.children}</PrimaryContent>
          <GlobalStyle />
        </StyledPage>
      </ThemeProvider>
    );
  }
}

Then my header file:

class Header extends React.Component {
  render() {
    return (
      <HeaderSide

        <HeaderToggleBar onClick={() => this.props.toggle()} />

      </HeaderSide>
    );
  }
}

So the app starts off with navActive state of false, clicking the HeaderToggleBar element opens and closes the nav. But I need to close the nav when the route changes. I guess I could put the click event on the navigation items within the header (so clicking to a new page toggles) but that seems a bit over the top.

Thank you.

Upvotes: 48

Views: 76846

Answers (10)

Hashir Akbar
Hashir Akbar

Reputation: 131

For the latest Next 14+ we have next/navigation now for app router in order to detect & do something on client components (Only on client components) we can use this as it is in documentation of next js

Link to documentation

'use client'
 
import { usePathname, useSearchParams } from 'next/navigation'
 
function ExampleClientComponent() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
  useEffect(() => {
    // Do something here...
  }, [pathname, searchParams])
}

Upvotes: 0

PRAVEEN KUMAR T
PRAVEEN KUMAR T

Reputation: 493

For Nextjs 13 +

'use client';
import { useEffect } from 'react';
import { useSelectedLayoutSegment } from 'next/navigation';

const activeSegment = useSelectedLayoutSegment();


useEffect(() => {
    console.log('>>>>>');
}, [activeSegment]);

Upvotes: 3

Ismael Kondombo
Ismael Kondombo

Reputation: 61

Here is how we can detect page changes in Next.js 13 App folder.

You can listen for page changes by composing other Client Component hooks like usePathname and useSearchParams.

useSearchParams } from 'next/navigation'

export function NavigationEvents() {
    const pathname = usePathname() 
    const searchParams = useSearchParams()
    
    useEffect(() => {
        const url = `${pathname}?${searchParams}`
        console.log(url)
        // You can now use the current URL
        // ...
    }, [pathname, searchParams])

    return null } ```

Link of the doc: useRouter docs

It works for me.

Upvotes: 6

raptoria7
raptoria7

Reputation: 959

if you're using a functional React component, you can do this:

const router = useRouter();

useEffect(() => {
  if (isMenuOpen) {
    setMenuOpen(!isMenuOpen);
  }
}, [router.asPath]);

Upvotes: 53

Yilmaz
Yilmaz

Reputation: 49182

when route changing is happening, next.js is trying to load the other page. A better way to handle the route change, if you are tracking the loading state, keep that loading state true, until the route change is complete.

 const router = useRouter();

 useEffect(() => {
    const handleComplete = () => {
      setIsLoading(false);
    };
    router.events.on("routeChangeComplete", handleComplete);
    router.events.on("routeChangeError", handleComplete);

    return () => {
      router.events.off("routeChangeComplete", handleComplete);
      router.events.off("routeChangeError", handleComplete);
    };
  }, [router]);

Upvotes: 4

Webber
Webber

Reputation: 5484

Complete example

Using a functional component.

Triggering on routeChangeStart, so the menu can optionally be animated immediately after clicking.

import { useRouter } from 'next/router';

const Menu = () => {
  const router = useRouter();
  const [show, setShow] = useState(false);

  const hide = useCallback(() => {
    setShow(false);
  }, [setShow]);

  useEffect(() => {
    // subscribe
    router.events.on('routeChangeStart', hide);
    
    // unsubscribe
    return () => router.events.off('routeChangeStart', hide);
  }, [hide, router.events]);

  return show ? <div>menu</div> : null
}

Managing nesting (TypeScript)

I needed something like this in top level, though I'd share:

interface ContextProps {
  show: boolean;
  setShow: (show: boolean) => void;
};

export const MenuContext = React.createContext<Partial<ContextProps>>({
  show: false,
  setShow: () => {},
});
  const [show, setShow] = useState(false);

  return (
    <MenuContext.Provider value={{ show, setShow }}>
      {children}
    </MenuContext.Provider>
  )

Upvotes: 6

Mahadi Hasan
Mahadi Hasan

Reputation: 19

Reference: https://nextjs.org/docs/api-reference/next/router

There you can see class component base code. But here I use the functional components to solve this issue.

useEffect(() => {
    const handleRouteChange = () => {
        setOpenDropdown(false)
    }
    router.events.on('routeChangeStart', handleRouteChange)
}, [])

Upvotes: 1

One Mad Geek
One Mad Geek

Reputation: 529

We can use router events to handle this. Refer: https://nextjs.org/docs/api-reference/next/router#usage-6

const router = useRouter();
useEffect(() => {
  router.events.on('routeChangeComplete', handleRouteChange)
  return () => {
    router.events.off('routeChangeComplete', handleRouteChange)
  };
}, [router.events]);

Upvotes: 16

Rob
Rob

Reputation: 5481

Have a look at https://github.com/zeit/next.js/#router-events. You should be fine with sth like this in the ctor of your Header component:

constructor(props){
  super(props);

  Router.events.on('routeChangeComplete', (url) => {
    props.toggle();
  });
}

Upvotes: 22

Brock
Brock

Reputation: 47

I believe you need to bind your callback. You might also need to add a constructor. Here's an snippet from a project that accepts file uploads.

    class Files extends React.Component {
        // define your constructor and call super before setting state vars
        constructor(props){
            super(props);
            this.state = {
                uploadStatus: false
            }
            // Bind your callback
            this.handleUploadImage = this.handleUploadImage.bind(this);
            // set other initial vars if needed
        }

        handleUploadImage(files){
            // do stuff with the files
        }

        render () {
            return (
                <div id="fileContainer">
                    <MyDropzone id={this.props.project._id} onDrop={this.handleUploadImage}>
                        {({getRootProps}) => <div {...getRootProps()} />}
                    </MyDropzone>
                </div>
            )
        }
    }

Upvotes: 0

Related Questions