Running Function At Start of React Ionic 6 Application

Hi!
I’ve been developing an app (React, Ionic 6) that gives rankings to the user and lets them update them for ranking predictions. I also save the data gotten when the rankings are fetched to Ionic Storage, so the user is able to restore data once the app is reloaded.

This is the screen that is shown when the app has been loaded:

As seen, the user needs to manually click “Restore Data” after reloading the app. Is there a way to run the restore data function at the beginning of the program?

App.tsx code
import { Redirect, Route } from 'react-router-dom';
import {
  IonApp,
  IonIcon,
  IonLabel,
  IonRouterOutlet,
  IonTabBar,
  IonTabButton,
  IonTabs,
  setupIonicReact
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { /*ellipse, square,*/ triangle } from 'ionicons/icons';
import Rankings from './pages/Rankings';
// import Tab2 from './pages/Tab2';
// import Tab3 from './pages/Tab3';

/* 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';

setupIonicReact();

const App: React.FC = () => (
  <IonApp>
    <IonReactRouter>
      <IonTabs>
        <IonRouterOutlet>
          <Route exact path="/rankings">
            <Rankings />
          </Route>
          <Route exact path="/">
            <Redirect to="/rankings" />
          </Route>
        </IonRouterOutlet>
        <IonTabBar slot="bottom">
          <IonTabButton tab="rankings" href="/rankings">
            <IonIcon icon={triangle} />
            <IonLabel>Rankings</IonLabel>
          </IonTabButton>
        </IonTabBar>
      </IonTabs>
    </IonReactRouter>
  </IonApp>
);

export default App;
Rankings.tsx code (the main tab element)
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonList, IonItem, IonLabel, IonInput, IonButton, IonGrid, IonCol, IonRow } from '@ionic/react';
import { Storage } from '@ionic/storage';
import { useState } from 'react';
import { events } from 'robotevents-api';

import './Rankings.css';
import { Rankings } from 'robotevents-api/lib/api/rankings';

const store = new Storage();
store.create();

const RankingsTab: React.FC = () => {
  const [inputSKU1, setInputSKU1] = useState<string | undefined>("VRC");
  const [inputSKU2, setInputSKU2] = useState<string | undefined>("22");
  const [inputSKU3, setInputSKU3] = useState<string | undefined>("0008");
  const [division, setDivision] = useState<string | undefined>("1");
  const [dataRestored, setDataRestored] = useState<boolean>(false);

  const [actualRankings, setActualRankings] = useState<Rankings[]>([]);
  const [rankings, setRankings] = useState<Rankings[]>([]);

  const restoreData = async () => {
    const json: Rankings[] = await store.get('rankings');
    console.log('Rankings have been recieved from local storage.');

    setInputSKU1(await store.get("Input SKU 1"));
    setInputSKU2(await store.get("Input SKU 2"));
    setInputSKU3(await store.get("Input SKU 3"));
    setDivision(await store.get("Division"));
    console.log('Input data has been recieved from local storage.');
    
    setActualRankings(json.reverse())
    setRankings(organizeRankings(json));

    setDataRestored(true);
  };

  const getData = async () => {
    if ( inputSKU1 == undefined || inputSKU1 == "" || 
      inputSKU2 == undefined || inputSKU2 == "" ||
      inputSKU3 == undefined || inputSKU3 == "") {
      alert("Please enter in the event SKU.");
      return;
    }

    const json: Rankings[] = await (await events.get(`RE-${inputSKU1}-${inputSKU2}-${inputSKU3}`)).rankings({ divId: parseInt(division!) || 1 });
    for (let i = 0; i < json.length; i++) { // @ts-ignore
      json[i].wpcolor = 'inherit'; // @ts-ignore
      json[i].apcolor = 'inherit'; // @ts-ignore
      json[i].spcolor = 'inherit';
    }

    setActualRankings(json.reverse())
    setRankings(organizeRankings(json));

    setDataRestored(true);
    
    await store.set('rankings', json);
    console.log('Rankings have been saved to local storage.');

    await store.set("Input SKU 1", inputSKU1);
    await store.set("Input SKU 2", inputSKU2);
    await store.set("Input SKU 3", inputSKU3);
    await store.set("Division", division);
    console.log('Input data has been saved to local storage.');
  };

  const organizeRankings = (ranking: Rankings[]) => {
    let newRanking: Rankings[] = [];
    for (let i = 0; i < ranking.length; i++) {
      let inserted = false;
      for (let j = 0; j < newRanking.length; j++) {
        if (ranking[i].wp > newRanking[j].wp) inserted = true;
        else if (ranking[i].wp == newRanking[j].wp) {
          if (ranking[i].ap > newRanking[j].ap) inserted = true;
          else if (ranking[i].ap == newRanking[j].ap && ranking[i].sp > newRanking[j].sp) inserted = true;
        }
        
        if (inserted) {
          newRanking = newRanking.slice(0, j)
                       .concat(ranking[i])
                       .concat(newRanking.slice(j));
          break;
        }
      }
      if (!inserted) newRanking.push(ranking[i]);
    }
    return newRanking
  };

  const reset = () => {
    setRankings(organizeRankings(actualRankings));
  };

  const changeState = (name: string, str: string | number | undefined | null, callback: Function) => {
    console.log(`'${name}' = '${str}'`);
    if (typeof str == 'string')
      callback(str);
    callback(str?.toString());
  };

  const changeRankings = (name: string, str: string | number | undefined | null, index: number) => {
    console.log(`'${index}-${name}' = '${str}'`);
    const newRankings = JSON.parse(JSON.stringify(rankings));

    if (str == '') { // @ts-ignore
      newRankings[index][name] = actualRankings[newRankings[index].rank - 1][name];
    }
    else if (typeof str == 'number')
      newRankings[index][name] = str;
    else if (parseInt(str!).toString().length != str!.length) { // @ts-ignore
      newRankings[index][name] = actualRankings[newRankings[index].rank - 1][name];
    }
    else
      newRankings[index][name] = parseInt(str!);

    // @ts-ignore
    if (actualRankings[newRankings[index].rank - 1][name] == newRankings[index][name])
      newRankings[index][`${name}color`] = 'inherit'; // @ts-ignore
    else if (actualRankings[newRankings[index].rank - 1][name] > newRankings[index][name])
      newRankings[index][`${name}color`] = 'red';
    else
      newRankings[index][`${name}color`] = 'lightgreen';

    setRankings(organizeRankings(newRankings));
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Rankings</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent fullscreen>
        <IonHeader collapse="condense">
          <IonToolbar>
            <IonTitle size="large">Rankings</IonTitle>
          </IonToolbar>
        </IonHeader>
        <IonList>
          <IonItem>
            <IonLabel>Event SKU/Code:</IonLabel>
            <IonInput class="top" readonly value="RE"></IonInput>
            <IonLabel>-</IonLabel>
            <IonInput class="top" type="text" value={inputSKU1} placeholder="XXX" onIonChange={e => changeState("Input SKU 1", e.target.value!, setInputSKU1)}></IonInput>
            <IonLabel>-</IonLabel>
            <IonInput class="top" type="number" value={inputSKU2} placeholder="00" onIonChange={e => changeState("Input SKU 2", e.target.value!, setInputSKU2)}></IonInput>
            <IonLabel>-</IonLabel>
            <IonInput class="top" type="number" value={inputSKU3} placeholder="0000" onIonChange={e => changeState("Input SKU 3", e.target.value!, setInputSKU3)}></IonInput>
          </IonItem>
          <IonItem>
            <IonLabel>Division: </IonLabel>
            <IonInput class="left" value={division} placeholder="1" onIonChange={e => changeState("Division", e.target.value!, setDivision)}></IonInput>
          </IonItem>
          <IonItem>
            <IonLabel></IonLabel>
            <IonButton onClick={getData} shape="round" size="default" color="success">Refresh Competition</IonButton>
            &nbsp;
            <IonButton onClick={reset} shape="round" size="default" color="warning">Reset</IonButton>
            <IonLabel></IonLabel>
          </IonItem>
        </IonList>

        <hr></hr>
        
        <IonGrid>
          <IonRow class="header">
            <IonCol size="1.2">#</IonCol>
            <IonCol size="2.8">Number</IonCol>
            <IonCol size="2.6">W-L-T</IonCol>
            <IonCol size="1.8">WP</IonCol>
            <IonCol size="1.8">AP</IonCol>
            <IonCol size="1.8">SP</IonCol>
          </IonRow>
          <IonRow><IonCol><IonButton hidden={dataRestored} onClick={restoreData} shape="round" size="default" color="danger">Restore Data</IonButton></IonCol></IonRow>
          {
            rankings.map((singleRanking, index) => (
              <IonRow key={singleRanking.team.id}>
                <IonCol size="1.2">{singleRanking.rank}</IonCol>
                <IonCol size="2.8">{singleRanking.team.name}</IonCol>
                <IonCol size="2.6">{singleRanking.wins}-{singleRanking.losses}-{singleRanking.ties}</IonCol> {/*@ts-ignore*/}
                <IonCol size="1.8"><IonInput type="number" class="teamlist" style={{color: singleRanking.wpcolor}} value={singleRanking.wp} placeholder={actualRankings[rankings[index].rank - 1]?.wp?.toString() || "0"} onKeyUp={e => { if (e.key === 'Enter') changeRankings('wp', e.target.value, index); }} onIonBlur={e => changeRankings('wp', e.target.value, index)}></IonInput></IonCol> {/*@ts-ignore*/}
                <IonCol size="1.8"><IonInput type="number" class="teamlist" style={{color: singleRanking.apcolor}} value={singleRanking.ap} placeholder={actualRankings[rankings[index].rank - 1]?.ap?.toString() || "0"} onKeyUp={e => { if (e.key === 'Enter') changeRankings('ap', e.target.value, index); }} onIonBlur={e => changeRankings('ap', e.target.value, index)}></IonInput></IonCol> {/*@ts-ignore*/}
                <IonCol size="1.8"><IonInput type="number" class="teamlist" style={{color: singleRanking.spcolor}} value={singleRanking.sp} placeholder={actualRankings[rankings[index].rank - 1]?.sp?.toString() || "0"} onKeyUp={e => { if (e.key === 'Enter') changeRankings('sp', e.target.value, index); }} onIonBlur={e => changeRankings('sp', e.target.value, index)}></IonInput></IonCol>
              </IonRow>
            ))
          }
        </IonGrid>
      </IonContent>
    </IonPage>
  );
};

export default RankingsTab;

The Rankings.tsx file contains the restoreData function towards the beginning of the file.

Thanks in advance!

One standard way to run a function at the start of an event (such as the start of the program) is to use the useEffect() hook. You can use useEffect() in combination with useState() to store the value, and then pass the stored down as a prop to the components where you need it (RankingsTab).

If you don’t want to pass the state down as a prop, you can use Context in combination with the other hooks.

Even if you’re using Ionic Storage, there’s nothing specific you need to do for Ionic to get this to work; you can use the standard state management tools for React.

But how would you actually update it only once, at the start of the program?

Are you new to React? You can make a root-level component that includes <IonApp> and use useEffect() there. For a more advanced solution, you can use tanstack-query .

1 Like