I'm a new developer and this is my first question on stack overflow so please be patient. I hope this is the right community and the correct way to ask a question (let me know if there are ways I can imporve my inquiries).
So I have a React functional compoenent that renders three forms conditionally. I took one big form and broke it into three sections so that when the user clicks the next button in the div, they are taken to the next form and the next button calls a click handler function that uses state to determine which form should be shown with a tertiary operator to check the state. My problem is that the logic of the handler function shows the third form after first click instead of the second as I would expect, any idea of a simpler work around or how to fix this? I have the code below and simplified it as much as I could to try and just show the logic issue I'm dealing with.
//Code
import { useState } from "react";
export default function PinFormTest(){
const [showFirstForm, setShowFirstForm] = useState(true)
const [showSecondForm, setShowSecondForm] = useState(false)
const [showThirdForm, setShowThirdForm] = useState(false)
const handleNextForm = () => {
showFirstForm ? setShowFirstForm(false) && setShowSecondForm(true):
showSecondForm ? setShowSecondForm(false) && setShowThirdForm(true):
console.log("error")
}
return(
<>
<div className="formContainer">
{showFirstForm ?
(<>firstForm</>): showSecondForm ? (<>secondForm</>): (<>thirdForm</>)
}
</div>
<div>
<button onClick={() => handlePreviousForm()}>Previous</button>
<button onClick={() => handleNextForm()}>Next</button>
</div>
</>)
}
In such situations try to avoid using the ternary operator because it will cause issues like readability, which makes it harder to debug later on.
const handleNextForm = () => {
if (showFirstForm) {
setShowFirstForm(false);
setShowSecondForm(true);
return;
}
if (showSecondForm) {
setShowSecondForm(false);
setShowThirdForm(true);
return;
}
console.log("error");
};
You should write multiple lines of statements in ternary operators by writing comma separated statements. Your correct code will be;
const handleNextForm = () => {
showFirstForm
? (setShowFirstForm(false), setShowSecondForm(true))
: showSecondForm
? (setShowSecondForm(false), setShowThirdForm(true))
: console.error(error);
};
You are using && and it will only evaluate right only if the left is true
left&&right// Example
So you are using setShowFirstForm(false) && setShowSecondForm(true) so this will first evaluate setShowFirstForm(false) from which you are not returning anything so the return value will be undefined which is a falsy value. So this will evaluate setShowSecondForm(true)
Solution 1
You can use , here as: Codesandbox demo
showFirstForm
? (setShowFirstForm(false), setShowSecondForm(true))
: showSecondForm
? (setShowSecondForm(false), setShowThirdForm(true))
: console.log("error");
Solution 2
You can use simple if-else branching, Codesandbox demo
if (showFirstForm) {
setShowFirstForm(false);
setShowSecondForm(true);
} else if (showSecondForm) {
setShowSecondForm(false);
setShowThirdForm(true);
} else {
console.log("error");
}
Solution 3
You can use Map here to create a map for form level and the component to render, other solution won't be a better solution if the stages in form increases. Codesandbox link
import React, { useState } from "react";
import FirstForm from "./components/FirstForm";
import SecondForm from "./components/SecondForm";
import ThirdForm from "./components/ThirdForm";
const formComponentsArr = [FirstForm, SecondForm, ThirdForm];
const formComponents = new Map(
formComponentsArr.map((comp, i) => {
return [i + 1, comp];
})
);
export default function App() {
const [level, setLevel] = useState(1);
function handleNextForm() {
if (level !== formComponents.size) {
setLevel((l) => l + 1);
}
}
function getComponent() {
const comp = formComponents.get(level);
return comp ? comp() : null;
}
return (
<>
<div className="formContainer">{getComponent()}</div>
<div>
{/* <button onClick={() => handlePreviousForm()}>Previous</button> */}
<button onClick={() => handleNextForm()}>Next</button>
</div>
</>
);
}
Solution 4 - BEST SULUTION
Create a custom hook as which will handle the next and previous. All you have to do is to pass an array of components that needs to be rendered sequentially. Codesandbox link