reactjsionic-frameworkreact-routerhistory

Using Ionic 7 and React 18, how do I access the history object to navigate between pages?


I'm using Ionic 7 and React 18. I want to build a drop down component such that when an option is selected, my URL will change to include the ID of the selected option. I have this set up in my App.tsx file,

<IonHeader>
  <IonToolbar>
    <IonButtons slot="start">
      <IonButton>
        <IonIcon icon={logoReact} />
      </IonButton>
    </IonButtons>
    <IonTitle>My Title</IonTitle>
    <IonButtons slot="end">
      <IonButton>
        <IonIcon icon={search} />
      </IonButton>
      <IonButton>
        <IonIcon icon={personCircle} />
      </IonButton>
    </IonButtons>
  </IonToolbar>
  <IonToolbar>
    <CategorySelectComponent />
  </IonToolbar>
</IonHeader>
...
   <IonReactRouter>
      <IonRouterOutlet>
        <Route exact path="/home">
          <Home />
        </Route>
        <Route exact path="/">
          <Redirect to="/home" />
        </Route>
        <Route exact path="/cards/:categoryId">
          <CardSlideComponent />
        </Route>
      </IonRouterOutlet>

and my selection component is as such

import {
  IonContent,
  IonItem,
  IonLabel,
  IonPage,
  IonSelect,
  IonSelectOption,
} from "@ionic/react";
import React, { useEffect } from "react";
import { useState } from "react";
import axios from "axios";
import CardService from "../services/CardService";
import { useHistory } from "react-router-dom";

const CategorySelectComponent: React.FC = () => {
  const [selectedValue, setSelectedValue] = useState<string | undefined>(
    undefined
  );
  const [options, setOptions] = useState<Category[]>([]);

  const history = useHistory();
  console.log(history); // this is always undefined

  const handleSelectionChange = (event: CustomEvent) => {
    const selectedCategoryId = event.detail.value;
    setSelectedValue(selectedCategoryId);

    // Navigate to the selected category page
    console.log("handle change ...");
    console.log(history);
    history.push(`/cards/${selectedCategoryId}`);
  };

  useEffect(() => {
    CardService.getCategories(setOptions);
  }, []); // Empty dependency array means this effect runs once on component mount

  return (
    <IonPage>
      <IonContent>
        <IonItem>
          <IonLabel>Choose an option:</IonLabel>
          <IonSelect
            value={selectedValue}
            onIonChange={handleSelectionChange}
            interface="popover"
          >
            {options.map((category) => (
              <IonSelectOption key={category.id} value={category.id}>
                {category.name}
              </IonSelectOption>
            ))}
          </IonSelect>
        </IonItem>

        <IonItem>
          <IonLabel>Selected Value:</IonLabel>
          <IonLabel>{selectedValue}</IonLabel>
        </IonItem>
      </IonContent>
    </IonPage>
  );

but the "history" component is consistently "undefined". Is there a more "ionic" way of doing this that doesn't involve the history object?


Solution

  • I ran into this same issue recently when needing to use the useIonRouter hook to check if back navigations were possible. You just need to promote the router component (that provides the routing context, e.g. the history object) higher in the ReactTree than any component consuming it.

    Tuck the IonHeader component inside the IonReactRouter component so that CategorySelectComponent is rendered within its routing context.

    <IonReactRouter>
      ...
    
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonButton>
              <IonIcon icon={logoReact} />
            </IonButton>
          </IonButtons>
          <IonTitle>My Title</IonTitle>
          <IonButtons slot="end">
            <IonButton>
              <IonIcon icon={search} />
            </IonButton>
            <IonButton>
              <IonIcon icon={personCircle} />
            </IonButton>
          </IonButtons>
        </IonToolbar>
        <IonToolbar>
          <CategorySelectComponent />
        </IonToolbar>
      </IonHeader>
      ...
       
      <IonRouterOutlet>
        <Route exact path="/home">
          <Home />
        </Route>
        <Route exact path="/">
          <Redirect to="/home" />
        </Route>
        <Route exact path="/cards/:categoryId">
          <CardSlideComponent />
        </Route>
      </IonRouterOutlet>
    </IonReactRouter>