1

When I use the <Link> tag in NextJs to navigate between pages it doesn't rerun my scripts after changing pages. It only runs the scripts after the first page load or when I press reload. When using plain <a> tags instead, it works fine because the page reloads after each navigation. As far as I can tell this happens because the <Link> tag makes it a Single-Page Application and doesn't refresh the page when navigating between pages.

I would greatly appreciate anyway to have it rerun the scripts when navigating between pages or to have it refresh the page without using just plain <a> tags and losing the Single-Page Application functionality.

This code doesn't refresh the page

 <Link href="/page1">
   <a>Page 1</a>
 </Link>
 <Link href="/page2">
   <a>Page 2 </a>
 </Link>

This code does refresh the page

 <a href="/page1">Page 1</a>
 <a href="/page2">Page 2 </a>

I load in all my scripts using a scripts component

export default const MyScripts = () => {
    
    return (
        <Script
            strategy="afterInteractive"
            type="module"
            src="/scripts/myScript.js"
            onReady={() => {
                console.log("Ready")
            }}
        /> 
    )
}

One thing I've noticed is that the above onReady function fires each time I change pages. Maybe someone knows how to run the myScript.js from the onReady.

Dan P
  • 43
  • 7

3 Answers3

1

I used to this to solve it. It rerun what you want to be rerun each time the URL changes.

function name(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
       // YOUR CUSTOM HOOK / FUNCTION
       console.log('I am called from pushStateHook');
       return pushState.apply(history, arguments);
    };
})
name(history)

My implementation in myScript.js

function myFunctions(){
    //my code
}

function urlChange(history){
    let pushState = history.pushState;
    history.pushState = function(state) {
        myFunctions();//I insert my code here
        return pushState.apply(history, arguments);
    };
}
urlChange(history);

Also I'm pretty sure history.pushState = function(state){} is what runs each time the url changes not the urlChange() function.

Dan P
  • 43
  • 7
  • @juliomalves nope this still uses the Nextjs router. I just put it inside the external script and put all the functions I needed to run on each page reload into the "// YOUR CUSTOM HOOK / FUNCTION" portion. I just updated the answer to show exactly how I implemented it. – Dan P Nov 06 '22 at 20:39
  • Right, I think I see what you mean. Thanks for clarifying. – juliomalves Nov 06 '22 at 21:18
  • @DanP nice hack. What's the `urlChange(history);` _(last line)_ for though? It looks like it will give an error since `history` is not defined. – ADTC Dec 19 '22 at 14:20
  • @ADTC nope that line is required. `history` is an predefined object under the window object [link](https://www.javatpoint.com/javascript-history-object) – Dan P Dec 21 '22 at 02:05
  • I think the mistake is you're declaring the function but also trying to call it on `window.history`. That doesn't look valid, and it doesn't do anything. You don't need that part `(window.history);` after the function declaration. – ADTC Dec 21 '22 at 10:06
  • @ADTC yep your right. You don't need the `(window.history)` and `urlChange(history)`. I changed it to be just `(history)`. – Dan P Dec 22 '22 at 17:45
  • It's still wrong. You cannot declare a named function and call it for execution in the same statement. [(See here.)](https://i.stack.imgur.com/DtZfo.png) It has to be separate. [(See here for the correct code.)](https://i.stack.imgur.com/TcWiy.png) – ADTC Dec 25 '22 at 16:10
  • @ADTC My bad, I didn't notice that. Should be good now. – Dan P Dec 26 '22 at 16:22
1

Caution: The hack of using history.pushState will not work reliably, especially when DOM elements need to be fetched using document.querySelector*. Anyway, it's also better to avoid hacks if you can.

I think the correct and clean way to do this is to use the useEffect React hook (or componentDidMount in class-based components).

This idea was proposed here: samplep182's comment in: Normal scripts "" not working after page navigation · Issue #4477 · vercel/next.js

Simply move the Javascript code from your script tag into a useEffect block. Remove the script tag and any script file as you no longer need them.

import { useEffect } from 'react';

export default const MyScripts = () => {
  useEffect(() => {
    // Paste the contents of /scripts/myScript.js here (and remove that file)
  }, []);
}

Note that it's recommended to have an empty array [] as the dependency list, to execute this on page load only.

ADTC
  • 8,999
  • 5
  • 68
  • 93
  • thanks for this answer. I solved this while still learning react and didn't know about `useEffect`. – Dan P Dec 21 '22 at 02:07
  • You're welcome, and thank you for accepting. (May I also ask for an upvote? :P) To me it seems that running the script on component mounting seems to be more correct than trying to piggyback on browser history movements. React can efficiently handle the hook at the appropriate time. – ADTC Dec 21 '22 at 10:08
  • ill upvote once i get enough points. i need 15. – Dan P Dec 22 '22 at 17:47
  • You have 23 now. LOL – ADTC Dec 25 '22 at 16:13
0

Could try to use the router object from the useRouter hook. Here's how you'd do that:

export const YourComponent = () => {
  const router = useRouter();
  ...
  return (<>
    <button onClick={() => router.push({ pathname: '/page1' })}/>...</button>
    // Or you can also use a div if you don't want it behaving like a button
    <div onClick={() => router.push({ pathname: '/page1' })}/>...
  ...

If this doesn't work, then I'd check for what the code for the Scripts component is doing and how it's reacting to state.

Let me know if that helps!

saguirrews
  • 280
  • 1
  • 7