Route to Mapbox only works once, but when I come back to it, nothing happens

I’m trying to learn Ionic and React I’m combining a Map app and a notes app and having it so I navigate with an IonToolbar.

I have the root go to the map. I click on the Notes app and it goes to the Notes app. When I go back to the Map, nothing shows up although I do see console logs and the url changes. There is also a component that allows you to add to Notes called Add. I can go back and forth between Notes and Add. But I can’t go back to Map. Any help will be appreciated. I’m a beginner so this is probably a basic question but any pointers would be helpful.

Here is my App.js

import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet, IonTabButton, IonTabBar, IonLabel, IonIcon, IonTabs } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import Map from './pages/Map';
import Notes from './pages/Notes';
import Add from './pages/Add';
import { triangle, ellipse, square } from 'ionicons/icons';

/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';

/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Theme variables */
import './theme/variables.css';

function App () {

  return (
  <IonApp>
    <IonReactRouter>
      <IonTabs>
	      <IonRouterOutlet>
		<Route exact path="/map" component={Map}/>
		<Route exact path="/notes" component={Notes}/>
		<Route exact path="/add" component={Add}/>
		<Route exact path = "/" render={() => <Redirect to="/map"/>}/>
	      </IonRouterOutlet>
	      <IonTabBar slot='bottom'>
		<IonTabButton tab = 'Map' href='/map'>
		  <IonIcon icon={triangle} />
		  <IonLabel>Map</IonLabel>
		</IonTabButton>
		<IonTabButton tab = 'Notes' href='/notes'>
		  <IonIcon icon={square} />
		  <IonLabel>Notes</IonLabel>
		</IonTabButton>
	      </IonTabBar>
      </IonTabs>
    </IonReactRouter>
  </IonApp>
  );
}

export default App;

Here is my Map.js:

import ReactDOM from 'react-dom'
import React, { useRef, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';
import '../App.css';
import fetchFakeData from '../api/fetchFakeData';
import Popup from '../components/Popup';




mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;

const App = () => {
  const mapContainerRef = useRef(null);
  const popUpRef = useRef(new mapboxgl.Popup({ offset: 15}));


  //initialize map when component mounts
  useEffect(() => {
    console.log("I'm here")
    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      // See style options here: https://docs.mapbox.com/api/maps/#styles
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [-104.9876, 39.7405],
      zoom: 12.5,
    });
    
    // add navigation control (the +/- zoom buttons)
    map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
    
    map.on('load', () => {
      // add the data source for new a feature collection with no features
      map.addSource('random-points-data', {
	type: 'geojson',
	data: {
	  type: 'FeatureCollection',
	  features: [],
	},

      });
      // now add the layer, and referece the data source above by name
      map.addLayer({
	id: 'random-points-layer',
	source: 'random-points-data',
	type: 'symbol',
	layout: {
	  // full list of icons here: https://labs.mapbox.com/maki-icons
	  'icon-image': 'bakery-15', //this will put little croissants on our map
	  'icon-padding':0,
	  'icon-allow-overlap': true,
	  },
	});
      });

    map.on('moveend', async () =>{
      //get new center coordinates
      const { lng, lat } = map.getCenter();
      // fetch new data
      const results = await fetchFakeData({ longitude: lng, latitude: lat });
      // update 'random-points-data' source with new data
      // all layers that consume the 'random-points-data' data source will be updated
      map.getSource('random-points-data').setData(results);
    });
    map.on('click', 'random-points-layer', e => {
      if (e.features.length) {
	const feature = e.features[0];
	// create popup node
	const popupNode = document.createElement('div');
	ReactDOM.render(<Popup feature={feature} />, popupNode);
	// set popup on map
	popUpRef.current.setLngLat(feature.geometry.coordinates).setDOMContent(popupNode).addTo(map);
      }
    });
    map.on('mouseenter', 'random-points-layer', e => {
      if (e.features.length) {
	map.getCanvas().style.cursor = 'pointer';
      }

    });

    // reset cursor to default when user is no longer hovering over a clickable feature
    map.on('mouseleave', 'random-points-layer', () => {
      map.getCanvas().style.cursor = '';
    });

    // clean up on unmount
    return () => map.remove();
  }, []); //eslint-disable-line react-hooks/exhaustive-deps

  return <div className='map-container' ref={mapContainerRef} />;
};


export default App;

and here is my Notes.js

import { IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCheckbox, IonCol, IonContent, IonFab, IonFabButton, IonGrid, IonHeader, IonIcon, IonItem, IonPage, IonRow, IonSlide, IonSlides, IonToolbar } from '@ionic/react';
import styles from './Notes.module.scss';

import { addOutline, menuOutline, notificationsOutline, searchOutline } from "ionicons/icons";
import { getCategories, getNotes } from '../store/Selectors';
import { CategoryStore, NoteStore } from '../store';
import { markNote } from "../store/NoteStore";

const Notes = () => {

	const categories = CategoryStore.useState(getCategories);
	const notes = NoteStore.useState(getNotes);

	const getNoteStyle = (categoryID, isComplete = false) => {

		const categoryColor = categories.filter(category => category.id === categoryID)[0].color;

		return {

			"--background": categoryColor,
			"--background-checked": categoryColor,
			"--border-style": "none",
			"opacity": isComplete ? "0.6" : "1"
		}
	}

	return (
		<IonPage>
			<IonHeader>
				<IonToolbar>
					<IonButtons slot="start">
						<IonButton>
							<IonIcon icon={ menuOutline } />
						</IonButton>
					</IonButtons>

					<IonButtons slot="end">
						<IonButton>
							<IonIcon icon={ searchOutline } />
						</IonButton>

						<IonButton>
							<IonIcon icon={ notificationsOutline } />
						</IonButton>
					</IonButtons>
				</IonToolbar>
			</IonHeader>
			<IonContent fullscreen>

				<IonGrid>
					<IonRow>
						<IonCol size="12" className="ion-padding-start">
							<h1 className={ styles.mainTitle }>Hello, Alan!</h1>
						</IonCol>
					</IonRow>

					<IonRow>
						<IonCol size="12" className="ion-padding-start ion-padding-top">
							<IonCardSubtitle className={ styles.heading }>
								Categories
							</IonCardSubtitle>
						</IonCol>
					</IonRow>
				</IonGrid>

				<IonSlides id="slider" options={{ slidesPerView: "auto", zoom: true, grabCursor: true }} className={ `${ styles.categorySlider } ion-padding-bottom` }>
					{ categories.map((category, index) => {

						const noteCount = notes.filter(n => n.category_id === category.id).length;

						return (

							<IonSlide key={ `categorySlide_${ index }`}>
								<IonCol className="ion-text-left">
									<IonCard>
										<IonCardHeader className="ion-no-padding">

											<div className={ styles.slideCount }>
												<h6>{ noteCount } { noteCount === 1 ? "note" : "notes" } </h6>
											</div>
											<div className={ styles.slideHeader }>
												<h4>{ category.name }</h4>
											</div>
										</IonCardHeader>

										<IonCardContent>
											<div className={ styles.categoryColor } style={{ borderBottom: `2px solid ${ category.color }` }}></div>
										</IonCardContent>
									</IonCard>
								</IonCol>
							</IonSlide>
						);
					})}
				</IonSlides>

				<IonGrid className={ styles.bottomContainer }>
					<IonRow>
						<IonCol size="12" className="ion-padding-start">
							<IonCardSubtitle className={ styles.heading }>
								Recent Notes
							</IonCardSubtitle>
						</IonCol>
					</IonRow>
					
					<div className={ styles.recentNotes }>

						{ notes.map((note, index) => {

							return (

								<IonRow key={ `note_${ index }` } className="animate__animated animate__faster" id={ `noteRow_${ note.id }` }>
									<IonCol size="12">
										<IonItem>
											<IonCheckbox checked={ note.complete } style={ getNoteStyle(note.category_id, note.complete) } onClick={ () => markNote(note.id) } />
											<h4 style={ note.complete ? { textDecoration: "line-through", opacity: "0.6" } : {} }>{ note.note }</h4>
										</IonItem>
									</IonCol>
								</IonRow>
							);
						})}
					</div>
				</IonGrid>

				<IonFab vertical="bottom" horizontal="end" slot="fixed" className="ion-padding">
					<IonFabButton routerLink="/add">
						<IonIcon icon={ addOutline } />
					</IonFabButton>
				</IonFab>
			</IonContent>
		</IonPage>
	);
};

export default Notes;

I hope I’m explaining myself, please let me know if any more of my code needs to be shown. Thanks for reading.

I have the same problem, but with Vue. I had working project without ionic and after adding it, the map is showing only once. I changed the code due to documentation and am using router-link, with href is working properly, but in this case will have to many calls to mapbox … I thought it was related with onMounted, but created new component with onIonViewWillEnter and has the same behavior.

I would englobe the div element in ion-page and ion-content.
IonPage is mandatory to work with react-router to have route transition.
Please look at React Navigation: Router Link Redirect to Navigate to Another Page

<IonPage>
    <IonContent>
         <div className='map-container' ref={mapContainerRef} />;
    </IonContent>
</IonPage>

Can you check and my example code - GitHub - AnatoliA95/ionic-vue-mapbox at master . If you want to started it, you must set private access token in Map.vue. After that you can see when you open first map it’s showing but for the second one is not working … This is the problem in my project i think the route is not working correctly, but for now cannot fix it :frowning: