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