1

I'm struggling with Ionic's lifecycle events used with RouterLink.

I have my app data in a JSON file that renders each item using the url paramenters when the page is loaded. I need buttons to go to the next or previous item and refresh the page content, ie:

  • I'm on Item 1 (/lecture/item1) => click next => routerLink(/lecture/item2)
  • I'm on Item 1 (/lecture/item1) => click prev => routerLink(/lecture/item0) That means that each time i move to a page i need to get the params from the url (/lecture/:param) and update the page data. The problem i'm facing is that RouterLink isn't triggering any Page Leaving lifecycle, so my states doesn't change, also, i can't clean the states (i can do it through an href param, but the whole app is refreshed). I found similar threads on this, but some of them are outdated/still opened: Routerlink bug github.

Is there any way/workaround to reload the component and update the states passing different parameters?

I already tried using useHistory hook, history.replace (Source), and routerDirection="back" but none of them are triggering ion page leaving hooks. I also tried replacing IonRouterOutlet with Switch but this means not having some important ionic features.

Thank you in advance!!

Here is an extract of code for reference:

lectures.json (Array of objects)

{
    "list" : [ {}  ]
}

App.tsx

<IonApp>      
      <IonReactRouter>
        <IonSplitPane contentId="main">
          <Menu />
          
          <IonRouterOutlet id="main">
            <Route path="/" exact>
              <Redirect to="/lectures" />
            </Route>
            <Route path="/lectures" exact component={Home}></Route>
            <Route path="/lectures/:name" exact>
              <Lecture />
            </Route>
          </IonRouterOutlet>
        </IonSplitPane>
      </IonReactRouter>
    </IonApp>

Lecture.tsx


const Page: React.FC = () => {

  const [lecturaIndex, setLecturaIndex] = useState<number>(0);
  const [btnLinks, setBtnLinks] = useState<{next?: string , prev?: string }>({});

 useIonViewDidEnter(()=>{
    // Gets page params and use them to find current item in data json
    let current;
    current = lectures.list.find(lecture => lecture.title === match.params.name); 
    setLecturaIndex(current.id);
   // Set next and prev buttons url for routerLink
    setBtnLinks({
      next: current.id < 13 ? `/lectures/${languages.list[current.id+1].title}` : '/lectures', 
      prev: current.id > 0 ? `/lectures/${languages.list[current.id-1].title}` : '/lectures'
    });
   // function that Enables/disables navigation buttons depending on current index. ie: if i=0; can't go back
    indexCheck(current);
  })

// THESE ARE NEVER TRIGGERED WHEN GOING TO NEXT PARAM (/lectures/:param)
useIonViewWillLeave(()=>{
    console.log('will leave lecture');
  })

  useIonViewDidLeave(()=>{
    console.log('did leave lecture');
  })
  return(
    <IonPage>
      <IonContent>        
        <IonButton          
          disabled={nextBtnDisable}           
          routerLink={btnLinks.next}
          routerDirection="back"      
          >
        NEXT
        </IonButton>
      </IonContent>
    </IonPage>
  )
}


Drew Reese
  • 165,259
  • 14
  • 153
  • 181
tomigeme
  • 15
  • 3
  • I can't speak to the ionic framework, but normally a `useEffect` with dependency on the route match param is sufficient for issuing side-effects like refetching relevant data. – Drew Reese Dec 01 '21 at 00:43
  • @DrewReese Thanks! that could work, i'm going to try that approach and post an update, however, i would prefer using Ionic lifecycle hooks since they give extra/more precise functionalities inside an Ionic app, deps can be passed to it, so it could work! – tomigeme Dec 01 '21 at 01:53
  • just use history to navigate between the pages if there is a bug in routerLink? – Aaron Saunders Dec 02 '21 at 05:43
  • I tried @DrewReese 's solution and it worked! However i had a bug and was able to make it not appear anymore but i dont have a clue how it fixed . I added [match.params] as dependency When navigating between next/prev /lectures/:param (same component), it works but when navigating to another url (ie root '/') (other component) it throws an error ('cant find param of null '). However if i use an arrow fn it seems to work ok! [() => Match.params]. Why is this happening? – tomigeme Dec 02 '21 at 16:51
  • `() => Match.params` is a new function reference each render cycle, so if used as a hook dependency will trigger the hook's callback each render cycle. – Drew Reese Dec 02 '21 at 16:59
  • Ah, I see, just did some reading up on these ionic hooks [here](https://ionicframework.com/docs/react/lifecycle#guidance-for-each-lifecycle-method) and I see that `ionViewWillEnter` and `ionViewDidEnter` are basically mounting lifecycle hooks, and don't seem like they'd trigger if you're already on a view and only changing URL path parameters. The `useEffect` is also called when mounting, and again when any specified dependencies update. If `match` is sometimes undefined then you may be able to use Optional Chaining, i.e. `[match?.params]`. – Drew Reese Dec 02 '21 at 17:06

1 Answers1

1

Normally a useEffect with dependency on the route match param is sufficient for issuing side-effects like refetching relevant data.

The ionic useIonViewWillEnter and useIonViewDidEnter hooks described here are basically mounting lifecycle hooks, and don't seem like they'd trigger if you're already on a view and only changing URL path parameters. Move the logic to a useEffect hook which is called when component mounts and when any of its specified dependencies update.

Example:

useEffect(() => {
  // business logic to handle name param changes
}, [match.params.name]);

If the match params is ever undefined then you can use Optional Chaining operator to guard against null/undefined object accesses.

useEffect(() => {
  // business logic to handle name param changes
}, [match?.params?.name]);

Update

It seems the useIonViewWillEnter and useIonViewDidEnter hooks do also take dependency arrays and internally implement a useEffect hook. (should have dug a bit more previously)

Source

export const useIonViewWillEnter = (callback: LifeCycleCallback, deps: any[] = []) => {
  const context = useContext(IonLifeCycleContext);
  const id = useRef<number | undefined>();
  id.current = id.current || Math.floor(Math.random() * 1000000);
  useEffect(() => {
    callback.id = id.current!;
    context.onIonViewWillEnter(callback);
  }, deps);
};

export const useIonViewDidEnter = (callback: LifeCycleCallback, deps: any[] = []) => {
  const context = useContext(IonLifeCycleContext);
  const id = useRef<number | undefined>();
  id.current = id.current || Math.floor(Math.random() * 1000000);
  useEffect(() => {
    callback.id = id.current!;
    context.onIonViewDidEnter(callback);
  }, deps);
};

The useEffect hook isn't necessary then.

useIonViewDidEnter(() => {
  // business logic to handle name param changes
}, [match?.params?.name]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 1
    I may add that the same params for useEffect applies to IonViewWill/Did enter like so: ``` useIonViewDidEnter(()=>{ // actions }, [match?.params?.name]) ``` – tomigeme Dec 02 '21 at 21:28
  • 1
    @tomigeme Nice! I should have dug a little deeper, sorry. – Drew Reese Dec 02 '21 at 21:42