How to fix Ionic List component flickering

I’m new to Ionic/React programming. I’m currently building an application that is connected to Google Firebase and makes automatic calls to the cloud firestore I have there.

I have a page that renders an Ionic component with two custom Ionic list components. The list has items which when clicked display an alert that prompts the user.

I noticed that whenever I click on the list and the alert opens the Ionic List component flickers when the Ion page refreshes to display the alert. The component also flickers when I close the alert.

Rulesets.tsx

const Rulesets: React.FC = () => {
  const [loading, setLoading] = useState(true)
  const [rulesList, setRulesList] = useState([])
  const [userRulesList, setUserRulesList] = useState([])
  const [downloadRules, setDownloadRules] = useState(false)
  const [deleteRules, setDeleteRules] = useState(false)
  const [focusedRulesetName, setFocusRulesetName] = useState("")
  const [focusedRulesetId, setFocusRulesetId] = useState("")
  const [focusedRulesetVersion, setFocusRulesetVer] = useState("")

  useEffect(() => {
    getUserRulesets().then((rulesets: any) => {
      if(rulesets){
        setUserRulesList(rulesets)
        console.log("Use effect setting user rules list.")
      }
      else{
        console.log("Use effect found no rulesets found for user.")
      }
    }).finally(() => setLoading(false))
  }, [])

  useEffect(() => {
    getRulesets().then((rulesets: any) => {
      if(rulesets){
        setRulesList(rulesets.map((rule: {rule: []}) => rule))
        console.log("Use effect setting available rules list.")
      }
    }).finally(() => setLoading(false))
  }, [])

  function updateUserRulesList(){
    getUserRulesets().then((rulesets: any) => {
      if(rulesets){
        console.log("Current user rules list: ",userRulesList)
        console.log("New user rules list: ", rulesets)
        if(rulesets === userRulesList){
          console.log("Rules unchanged.")
        }
        setUserRulesList(rulesets)
      }
      else{
        console.log("No rulesets found for user.")
        setUserRulesList([])
      }
    })
  }

  function openAlert(check: boolean, alertName: string, rulesetId: string, rulesetName: string, rulesetVer: string){
    setFocusRulesetName(rulesetName)
    setFocusRulesetId(rulesetId)
    setFocusRulesetVer(rulesetVer)
    
    if(alertName === "download"){
      setDownloadRules(check)
    }
    else{
      setDeleteRules(check)
    }
  }

  const RulesetItem = (prop: {id: string, name: string, version: string}) => {
    var installed = false
    var rule: {id: string}

    for(rule of userRulesList){
        if(rule.id === prop.id){
          installed = true
        }
      }
    
    if(installed){
      return (
        <IonItem key={prop.id+ "-available"}>
        <IonLabel>
          <h2>{prop.name}</h2>
          <h3>Version: {prop.version}</h3>
          <p>Installed</p>
        </IonLabel>
      </IonItem>
      )
    }
    return (
      <IonItem key={prop.id+ "-available"} button={true} onClick={() => openAlert(true, "download", prop.id, prop.name, prop.version)}>
      <IonLabel>
        <h2>{prop.name}</h2>
        <h3>Version: {prop.version}</h3>
      </IonLabel>
    </IonItem>
    )    
  }

  const DownloadAlert = () => {
    return (
      <IonAlert
        isOpen={downloadRules}
        onDidDismiss={() => setDownloadRules(false)}
        header={'Download Ruleset?'}
        subHeader={focusedRulesetName}
        message={'This will install this ruleset.'}
        buttons={[
          {
            text: 'Yes',
            handler: async () => {
              setDownloadRules(false)
              console.log("Downloading rules.")
              setLoading(true)
              await addRulesetToUser(focusedRulesetId, 
                focusedRulesetName,
                focusedRulesetVersion
              ).then(updateUserRulesList).finally(() => setLoading(false))
            }
          }, 
          {
            text: 'No'
          }
        ]}
      />
    )
  }

  const DeleteAlert = () => {
    return (
      <IonAlert
        isOpen={deleteRules}
        onDidDismiss={() => setDeleteRules(false)}
        header={'Delete Ruleset?'}
        subHeader={focusedRulesetName}
        message={'This will uninstall this ruleset.'}
        buttons={[
          {
            text: 'Yes',
            handler: async () => {
              setDeleteRules(false)
              console.log("Deleting rules.")
              console.log("Setting loading to true")
              setLoading(true)
              console.log("Loading set to true.")
              await removeRulesetFromUser(focusedRulesetId, 
                focusedRulesetName,
                focusedRulesetVersion
              ).then(updateUserRulesList).finally(() => setLoading(false))
            }
          }, 
          {
            text: 'No'
          }
        ]}
      />
    )
  }

  const InstalledRulesList = () => {
    if(userRulesList.length !== 0){
      return (
        <IonList lines="full" inset={true}>
        <IonListHeader lines="full">Installed Rules</IonListHeader>
        {userRulesList.map((rule: {id: string, name: string, version: string}) => (
          <IonItem key={rule.id + "-installed"}>
            <IonLabel>
              <h2>{rule.name}</h2>
              <h3>Version: {rule.version}</h3>
              <IonButton slot="end" onClick={() => openAlert(true, "delete", rule.id, rule.name, rule.version)}> 
                Delete
              </IonButton>
            </IonLabel>
          </IonItem>
        ))}
      </IonList>
      )
    }
    return null
  }

  const AvailableRulesList = () => { //Component that flickers
    console.log("Returning available rules.")
    console.log("Ruleslist: ",rulesList)
    return(
      <IonList lines="full" inset={true}>
        <IonListHeader lines="full">Available Rules</IonListHeader>
        {rulesList.map((rule: {id: string, data: {name: string, version: string}}) => (
          <RulesetItem key={rule.id + "ruleset-item"} id={rule.id} name={rule.data.name} version={rule.data.version}/>
        ))}
      </IonList>
    )
  }

  return (
    <IonPage>
      <MainHeader/>
      <IonContent className="ion-content-rulesets ion-padding" fullscreen>
        <InstalledRulesList/> {/*Custom list component*/}
        <AvailableRulesList/> {/*Custom list component*/}
        <DownloadAlert/>
        <DeleteAlert/> 
        <IonLoading duration={0} isOpen={loading}/>
      </IonContent>
    </IonPage>
  )
}

export default Rulesets

Hopefully that is enough information to go off of. I can add more if need be. Thanks in advance.

You’re updating state on alert open and close, which is going to cause a re-render. That shouldn’t necessarily cause a flicker, but the complexity of your state above is probably hiding some issues.

First thing to try would be deferring the state changes on open and close. Meaning try in openAlert to add a setTimeout or requestAnimationFrame around all the set methods.

An even better idea would be to refactor this into a bunch of smaller components. I find more than three useState in a component and things start to get unwieldy and it’s time to separate things into sub components.