Elvis operator in Ionic React does not work

I have 2 variables that determine which components to render. personalInfo and cliente.

The problem I have is that when personalInfo equals false, the CompleteInfo component is not displayed (rendered). Instead, HomeCliente or HomeProveedor is rendered depending on the client variable.

<Route path="/home" render={() =>   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) :  <Completarinfo setRenderAgain={setRenderAgain} />   )  } ></Route>

Apparently the operator elvis is walking backwards. What is the problem here?

return(
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main" when="(min-width: 4096px)">
      <Menu renderAgain={renderAgain} />
  <IonRouterOutlet id="main">

    <Route path="/home" render={() =>   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) :  <Completarinfo setRenderAgain={setRenderAgain} />   )  } ></Route>
  

  </IonRouterOutlet>
  </IonSplitPane>
</IonReactRouter>
</IonApp>

);
};

Disclaimer: I know zero about React.

Are you absolutely certain that your pathological case is really happening when personalInfo is false? Is there any possibility that it’s something that you, a reasonable human being, would think of as being false, but really isn’t?

For example, let’s say you get this nice JSON object from a backend:

{
  "foo": "false"
}

Those quotes will kill us.

$ node    
Welcome to Node.js v14.17.0.
> let afo = {"foo": "false"};
undefined
> afo.foo ? "gotcha" : "no problem";
'gotcha'

There are other similar pitfalls. An empty object {} or array [] is also not falsy.

It is false. Because Im seeing it by console:

const App: React.FC = () => {
  const [isReg, setIsReg] = useState(false);
  const [cliente, setCliente] = useState(true);
  const [personalInfo,setPersonalInfo]=useState(false)

  const [renderAgain, setRenderAgain] = useState(false);

 //removeItem("isRegistered");
 //removeItem("clientType");
  useEffect(() => {
    if(renderAgain){
      console.log("render again igual a true")

    }
    getItem("isRegistered").then(res => {
      if (res!=null){
        setIsReg(true);
        getItem("clientType").then(res => {
          //"tipo de cliente: "+res)
          if (res=="1"){
            //"entonces es igual a 1 el tipo de cliente")
            setCliente(true)
            getItem("personalInfoCompleted").then(res => {
              if(res!=null){
                console.log("PERSONAL INFO: "+ res)
                setPersonalInfo(res)
              }else{
                console.log("NO HAY PERSONAL INFO")
              }
            })
          }else{
            //"entonces es igual a 2 o 3 el tipo de cliente")
            setCliente(false)
            getItem("personalInfoCompleted").then(res => {
              if(res!=null){
                console.log("PERSONAL INFO: "+ res)
                setPersonalInfo(res)
              }else{
                console.log("NO HAY PERSONAL INFO")

              }

            })
          }
          
        })

      }
      else{
        setIsReg(false);
      }
    });
    console.log("aqui se ejecuto el use effect de apps.tsx")
}, [renderAgain, isReg,cliente ]);

return(
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main" when="(min-width: 4096px)">
      <Menu renderAgain={renderAgain} />
  <IonRouterOutlet id="main">

    <Route path="/" render={() => isReg ?   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) : <Completarinfo setRenderAgain={setRenderAgain} /> ) : <Inicio /> } />
    <Route path="/home" render={() =>   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) :  <Completarinfo setRenderAgain={setRenderAgain} />   )  } ></Route>
    
  </IonRouterOutlet>
  </IonSplitPane>
</IonReactRouter>
</IonApp>

);
};

export default App;

Somebody familiar with React is probably going to have to help you.

Apparently, this useState thing creates some magical framework-managed box that persists values across what Angular would call “change detection cycles”. You are apparently populating various incarnations of these boxes with the results of asynchronous events and yet trying to make definitive choices based on snapshots at arbitrary points in time.

To my amateur eyes, that would seem fraught with peril. Unless React provides some sort of guarantee otherwise, I would think that the previous value of each of these “state boxes” would still be in play until some code comes along to overwrite it.

To rephrase what I’m worried about, imagine we have three “sticky” booleans: sined, seeled and delivered. A combination of asynchronous operations populates each. Then, on each successive iteration, the old values are still in there.

Every time I write an arrow function, I mentally think of it as a message in a bottle. That helps me remember that I have ceded control of when it will be executed relative to anything outside its bottle. You have five arrow functions, which I’ll letter A-E in order of appearance. On each run of A, there will theoretically be a point in time where B has resolved, changed isReg to true, C has resolved, set cliente to true, and personalInfo could be absolutely anything, because D hasn’t resolved yet. Your console log won’t tell you anything, unless you know precisely when it is being rendered.

This is why I do my best to flatten future chains, instead of nesting them like you have here. While you wait for better answers from somebody who actually understands React, perhaps it would be worthwhile to try to rearchitect the code so that every single then is at the same indentation level - no nesting. It might seem harder to write, but I bet it would turn out easier to read, and may expose some implicit assumptions that are burning you.

1 Like

Thanks.

To avoid that I added a new endloading hook, which will be true when all asynchronous functions finish but the problem continues. Hence I think the problem is in the elvis operator

const App: React.FC = () => {
  const [isReg, setIsReg] = useState(false);
  const [cliente, setCliente] = useState(true);
  const [personalInfo,setPersonalInfo]=useState(false)

  const [renderAgain, setRenderAgain] = useState(false);
  
  const [endloading,setEndLoading] = useState(false)

 //removeItem("isRegistered");
 //removeItem("clientType");
  useEffect(() => {
    if(renderAgain){
      console.log("render again igual a true")

    }
    getItem("isRegistered").then(res => {
      if (res!=null){
        setIsReg(true);
        getItem("clientType").then(res => {
          //"tipo de cliente: "+res)
          if (res=="1"){
            //"entonces es igual a 1 el tipo de cliente")
            setCliente(true)
            getItem("personalInfoCompleted").then(res => {
              if(res!=null){
                setPersonalInfo(res)
                setEndLoading(true)
              }else{
                setEndLoading(true)

              }
            })
          }else{
            //"entonces es igual a 2 o 3 el tipo de cliente")
            setCliente(false)
            getItem("personalInfoCompleted").then(res => {
              if(res!=null){
                setPersonalInfo(res)
                setEndLoading(true)

              }else{
                setEndLoading(true)


              }

            })
          }
          
        })

      }
      else{
        setIsReg(false);
        setEndLoading(true)

      }
    });
}, [renderAgain, isReg,cliente , personalInfo]);

if(endloading){
  return(
    <IonApp>
    <IonReactRouter>
    <IonSplitPane contentId="main" when="(min-width: 4096px)">
          <Menu renderAgain={renderAgain} />
      <IonRouterOutlet id="main">
    
        <Route path="/" render={() => isReg ?   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) : <Completarinfo setRenderAgain={setRenderAgain} /> ) : <Inicio /> } />
        <Route path="/home" render={() =>   ( personalInfo ? (cliente ?  <HomeCliente /> : <HomeProveedor /> ) :  <Completarinfo setRenderAgain={setRenderAgain} />   )  } ></Route>
        <Route path="/registro" component={Registro} exact={true}></Route>
        <Route path="/ingresar" render={() => <Ingresar renderAgain={setRenderAgain} />} /> 
        <Route path="/MisServicios" component={MisServicios} exact={true}></Route>
        <Route path="/Favoritos" component={Favoritos} exact={true}></Route>
        <Route path="/HistorialServicios" component={HistorialServicios} exact={true}></Route>
        <Route path="/Completarinfo" render={() => <Completarinfo setRenderAgain={setRenderAgain} />}  />
        <Route path="/CompletarRubros" component={CompletarRubros} exact={true}  />
    
        <Route path="/inicio" component={Inicio} />
    
        <Route path="/tab2" component={Tab2} exact={true} />
    
      </IonRouterOutlet>
      </IonSplitPane>
    </IonReactRouter>
    </IonApp>
    
    );
}else{
  return(<></>)
}

};

If my suspicions are correct (and keep in mind that I’m probably less than 10% confident here), this wouldn’t change anything, as endloading is never set to false again, so it would be stickily true forever, once the first iteration of your whatever-you-call-this-behemoth-in-React completes.

you are making assumptions regarding the actual order of when setState is changing your values and you cannot, setState is asynchronous.

See this article, specifically regarding

optional second argument that expects a callback function which will only execute after the state updating merge of the first argument has completed.

React setState & its Async Nature | by Jeanette Abell | Geek Culture | May, 2021 | Medium

Also I would consider reachitectiing your router and also that useEffect code… you got a lot of stuff going in there

2 Likes

Thanks aaron.

Can I do something like this? Its a good programing?

const HomeClienteOProveedor = () => {

  const [cliente, setCliente] = useState(true)

  getItem("clientType").then(res => {
    
    if (res=="1"){
      
      setCliente(true)
    
    }else{
      
      setCliente(false)
    } 
  })
    

  useEffect(() => {

    if (cliente){
      return ( ()=>{
        <HomeCliente />
     });
    }else{
      return ( ()=>{
        <HomeProveedor />
     });
    }

}, [ cliente]);
    
  
}

But what I got is this: ‘HomeClienteOProveedor’ cannot be used as a JSX component.
Its return type ‘void’ is not a valid JSX element.

What I need is a way to render when useState finish. And As Im using tsx I dont use the link snotation.

I think there’s some underlying base React knowledge that you need to upskill in. I’ve refactored your code and merged your state into a single object, furthermore your logic in the useEffect is quite convoluted. Below is an untested example with several simplifications, the most important of which is not setting the state multiple times and repeatedly:

interface IData {
    isReg: boolean,
    personalInfo: boolean,
    cliente: boolean,
    renderAgain: boolean,
}

function App() {
  const [data, setData] = useState<IData>({
    isReg: false,
    personalInfo: false,
    cliente: true,
    renderAgain: false,
  })

  async function get() {
    // note that this function only calls "setData" once at most!
    // this is important as it reduces the amount of re-renders you will be making

    const isRegistered = await getItem('isRegistered')
    if (isRegistered === null) {
      setData((d) => ({...d, isReg: false}))
      return
    }

    const clientType = await getItem('clientType')
    const cliente = clientType === '1'

    const personalInfo = await getItem('personalInfoCompleted')

    setData((d)=> ({...d, isReg: true, cliente, personalInfo })
  }

  useEffect(() => {
    console.log(data)
    get()
    // note the empty dependency array below "[]" as it makes this only 
    // happen on first mount. Your previous example updated the state 
    // repeatedly which triggered your effect and you ended up in an 
    // endless loop
  }, [])

  return (
    <IonApp>
      <IonReactRouter>
        <IonSplitPane contentId='main' when='(min-width: 4096px)'>
          <Menu renderAgain={renderAgain} />
          <IonRouterOutlet id='main'>
            <Route path='/home' render={() => <HomePage {...data} />} / >
          </IonRouterOutlet>
        </IonSplitPane>
      </IonReactRouter>
    </IonApp>
  )
}

export default App

function HomePage({isReg, personalInfo, cliente}: IData) {
  if(!isReg) return <Inicio />
  if(!personalInfo) return <Completarinfo />
  if(cliente) return <HomeCliente />
  return <HomeProveedor />
}


2 Likes