I have created a form like structure for users to chose one box and then click next like this (all pictures below). I use a map to display the boxes and when click next I am using state to load the next answerboxes. I have also a title that I have above the map as an ordinary p-tag. The way I have tested this on is that I use a counter (also state object) that I increment every time next is clicked. But the questions are stored in arrays (se code test) and I dont know how to implement this so that I can have a flow and a working way for both the title and the arrays of questions to show up when I click next. I was thinking of using props somehow but I cant seem to figure out the way to do it. As it is now I can loop through one array (with title) and display answers in qboxes but when I want to add the next automatically I cannot do it in any smooth way.
(qbox-answer clicked and next button clicked)
code:
//For looping through and have qboxes created.
const testAnswers1 = ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"];
const testAnswers2 = ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"];
const testAnswers3 = ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"];
//Every array should have one title displayed until end
const questionTitles = ["title1", "title2", "title3", "title4"]
function ReportForm(props){
const [title, setTitle] = useState(titles);
const [qBoxes, setqBoxes] = useState(test);
//const [isMouseOver, setMouseOver] = useState(false);
//counter for title and qboxes
let [counter, setCounter] = useState(0);
//For storing answers
let [answers, setAnswers] = useState([]);
//Code for handling hovering over an answerbox (css) and also collect the clicked answer
const handleClick = (event) => {
if(event.currentTarget.classList.contains('active')) {
event.currentTarget.classList.remove('active');
answers.pop(event.currentTarget.value);
console.log(event.currentTarget.value);
} else {
event.currentTarget.classList.add('active');
setAnswers([...answers, event.target.value]);
}
event.preventDefault();
}
//Next button code
const submitNext = (event) => {
if (counter < titles.length -1){
setqBoxes(qboxes[counter]);
setCounter(counter = counter +1);
setTitle(event.target.value);
} else {
//resets counter to last value so nothing happens when next is clicked
setCounter(counter);
}
}
//Code for map function
return (
<div>
<p>{questionTitles[counter]}</p>
{qBoxes.map((e) => {
return (
<button
onClick={handleClick}
className="boxStyle"
name="contentYes"
value={e}
onchange={handleChange}
>{e}</button>
)
})};
<button className="buttonNext" onClick={submitNext}>Next</button>
<button className="buttonPrev" onClick={submitPrev}>Previous</button>
</div>
);
These things get much easier when using a pre-built form-handling library. Check out Formik or React-Hook-Form
For the wizard itself, consider wrapping each question (with answer array) in a separate component, so that it can't see or be bothered by the other questions.
Here is a recipe that is pretty declarative: Each question is its own component (you can map these from an array), and they are wrapped by a parent whose job it is to know where in the wizard the user is, what has been answered, and to only show the current question.
First of all, the Wrapper can use a state to remember where in the wizard it currently is
const increment = (x) => x + 1;
const decrement = (x) => x - 1;
const Wizard = ({ children }) => {
const maxCount = React.Children.count(children) - 1;
const minCount = 0;
const [current, setCurrent] = React.useState(minCount);
const incrementCurrent = () => setCurrent(increment);
const decrementCurrent = () => setCurrent(decrement);
const isMaxCount = current === maxCount;
const isMinCount = current === minCount;
...
Then, it can select the child component that corresponds to that state
...
return (
<fieldset className="wizard">
<legend>{name}</legend>
{React.Children.toArray(children).at(current)}
<button type="button" disabled={isMinCount} onClick={decrementCurrent}>
prev
</button>
<button type="button" disabled={isMaxCount} onClick={incrementCurrent}>
next
</button>
</fieldset>
)
You can try it out with just any children
<Wizard name="my wizard">
<span>how</span>
<span>YOU</span>
<span>doin'</span>
</Wizard>
Everything gets easier if we use a library to help us handle user input in form-like situations. It probably need some kind of initialisation, so we do that in the parent as well, and make sure to pass whatever controls or registering functions that the children (questions components) are going to need. In this example, I went with React-Hook-Form
const form = useForm();
const onSubmit = () => {
console.log(form.getValues())
}
return (
...
<form onSubmit={form.handleSubmit(onSubmit)}>
{React.cloneElement(React.Children.toArray(children).at(current), { form })}
...
Each question can now use that form prop and interact with the form
Eg text input
const DescribeMarcellus = ({ form }) => (
<article>
<h2>Describe what Marsellus Wallace looks like</h2>
<input type="text" {...form.register('description')} />
</article>
)
Or buttons like you have
const WhichCountry = ({ form }) => (
<article>
<h2>What country you from?</h2>
<div className="flexCenter">
{["What", "Tobleronistan", "Option 3"].map((ans) => (
<div
key={ans}
className="option"
style={{
backgroundColor: form.getValues("country") === ans ? "blue" : "green"
}}
onClick={() => form.setValue("country", ans)}
>
{ans}
</div>
))}
</div>
</article>
);
const SayWhatAgain = ({ form }) => (
<article>
<h2>
<i>{form.getValues("country")}</i> ain't no country I ever heard of! They
speak English in What?
</h2>
{["Yeah", "Nah"].map((ans) => (
<div key={ans}>
<input
type="radio"
id={ans}
name="contact"
value={ans}
{...form.register("english")}
/>
<label htmlFor={ans}>{ans}</label>
</div>
))}
</article>
);
Putting it all toghether:
import React from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
const increment = (x) => x + 1;
const decrement = (x) => x - 1;
const Wizard = ({ name, children }) => {
const maxCount = React.Children.count(children) - 1;
const minCount = 0;
const [current, setCurrent] = React.useState(minCount);
const incrementCurrent = () => setCurrent(increment);
const decrementCurrent = () => setCurrent(decrement);
const isMaxCount = current === maxCount;
const isMinCount = current === minCount;
const form = useForm();
const onSubmit = () => {
console.log(form.getValues());
};
return (
<fieldset className="wizard">
<legend>{name}</legend>
<form onSubmit={form.handleSubmit(onSubmit)}>
{React.cloneElement(React.Children.toArray(children).at(current), {
form
})}
<button type="button" disabled={isMinCount} onClick={decrementCurrent}>
prev
</button>
<button type="button" disabled={isMaxCount} onClick={incrementCurrent}>
next
</button>
<button type="submit">submit</button>
</form>
</fieldset>
);
};
const DescribeMarcellus = ({ form }) => (
<article>
<h2>Describe what Marsellus Wallace looks like</h2>
<input type="text" {...form.register("description")} />
</article>
);
const WhichCountry = ({ form }) => (
<article>
<h2>What country you from?</h2>
<div className="flexCenter">
{["What", "Tobleronistan", "Option 3"].map((ans) => (
<div
key={ans}
className="option"
style={{
backgroundColor: form.getValues("country") === ans ? "blue" : "green"
}}
onClick={() => form.setValue("country", ans)}
>
{ans}
</div>
))}
</div>
</article>
);
const SayWhatAgain = ({ form }) => (
<article>
<h2>
<i>{form.getValues("country")}</i> ain't no country I ever heard of! They
speak English in What?
</h2>
{["Yeah", "Nah"].map((ans) => (
<div key={ans}>
<input
type="radio"
id={ans}
name="contact"
value={ans}
{...form.register("english")}
/>
<label htmlFor={ans}>{ans}</label>
</div>
))}
</article>
);
export default function App() {
return (
<div className="App">
<Wizard name="my wizard">
<DescribeMarcellus />
<WhichCountry />
<SayWhatAgain />
</Wizard>
</div>
);
}
codeSanbox Link: https://codesandbox.io/s/simple-wizard-sspt9u?file=/src/App.js
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.