I am creating a multistep form / wizard with react and Formik. My initial values is an empty array, which I populate with answers from the form. I have this structure, because the data structure must be very specific (not of my choosing). The form is displayed one question at a time, so the user cannot see upcoming questions, only previous answers.
Previous answers should be editable, but in such a way that if an answer is edited, all following answers are reset and the user must start over with the form from the point where the form was edited. Hence, I need to get the index of the question where the value is being updated and remove all following values. Basically, I need to do:
const newArray = array.splice(index, array.length)
...but in Formik. Is this possible?
I have looked at enableReinitialize
but I only need to reset part of my form, at a given index.
All I can find in the docs are ways of removing or inserting values, but not to reset.
I have also tried versions of setFieldValues('answers', [])
but haven't gotten it to work.
All suggestions are much appreciated.
Here is my form component:
// lib
import { Formik, Form, Field, FieldArray } from 'formik';
import { motion, AnimatePresence, useScroll } from "framer-motion";
// components
import Question from './Question'
import ProgressBar from './ProgressBar'
// utils
import * as constants from '../Utils/constants';
const WizardBase = (props) => {
const ref = React.useRef(null);
const questionsToShow = 1;
const [next, setNext] = useState(questionsToShow - 1);
const [questions, setQuestions] = useState([]);
const [question, setQuestion] = useState([]);
const handleMoreQuestions = (index) => {
setNext(next + questionsToShow);
};
useEffect(() => {
props.loadQuestions();
}, []);
const questionGroups = props.wizardData.questions;
useEffect(() => {
if (questionGroups) {
questionGroups.map((sections, i) => {
sections.questions.map((question, index) => {
setQuestions(questions => [...questions, question]);
})
})
}
}, [questionGroups])
useEffect(() => {
if (questions.length > 0) {
questions.map((question, i) => {
setQuestion(question => [...questions, question])
})
}
}, [questions])
return (
<div className="pension-wizard-block">Pension insurance
<nav className="top-nav">
<ProgressBar total={questions.length} />
</nav>
<Formik initialValues={constants.initialValues}
onSubmit={(values) => { console.log(values) }}>
<Form>
<div className='pension-wizard-block__question'
ref={ref}
>Välkommen
<button onClick={handleMoreQuestions}>Börja</button>
</div>
<FieldArray name="Answers">
{({ insert, remove, push }) => (
question && question.length > 0 && question.slice(0, next).map((q, index) => (
<Question
key={q.id}
index={index}
legend={`Fråga ${index + 1}`}
question={q.questionText}
type='radio'
labels={q.options}
options={q.options}
id={`Answers.${index}.QuestionId`}
questionId={q.id}
name={`Answers.${index}.Answer`}
handleMoreQuestions={handleMoreQuestions}
total={questions.length}
/>
))
)}
</FieldArray>
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
)
}
This is my question component:
// lib
import { motion, useViewportScroll } from 'framer-motion';
import { throttle } from 'lodash';
// components
import QuestionInput from './QuestionInput'
const Question = ({
type,
name,
legend,
checked,
options,
question,
id,
questionId,
handleMoreQuestions,
index,
total
}) => {
const [answered, setAnswered] = useState(null);
const [scrollingUp, setScrollingUp] = useState(false)
// check scroll direction
let { scrollY } = useViewportScroll();
const update = useMemo(() => throttle((latest) => {
if (latest < 0) return;
let isScrollingDown = scrollY.getPrevious() - latest < 0;
let scrollDirection = isScrollingDown ? "down" : "up";
setScrollingUp(isScrollingDown)
}, 100), [scrollY]);
useEffect(() => {
scrollY.onChange(update);
}, [update]);
// scroll effect on answer
const refs = useRef([]);
useEffect(() => {
if (total) {
if (refs.current[index] && total >= index + 1) {
refs.current[index].scrollIntoView({ behavior: "smooth" });
} else {
console.log("end");
}
}
}, [index]);
// height animation on answer
const variant = {
open: {
height: '80vh',
},
closed: {
height: '100%',
}
}
return (
<motion.div
animate={answered ? "closed" : "open"}
variants={ variant }
className={`pension-wizard-block__question ${answered !== null ? "answered" : ""}`}
ref={(element) => (refs.current[index] = element)}
>
<fieldset className="pension-wizard-block__question__inner">
<legend>{legend}</legend>
<p>{question}</p>
{options && options.length > 0 && options.map((option, index) => (
<QuestionInput
key={index}
handleMoreQuestions={handleMoreQuestions}
index={index}
questionId={questionId}
id={id}
type={type}
name={name}
label={option.text}
handleAnswered={setAnswered}
total={total}
/>
))}
</fieldset>
</motion.div>
)
}
export default Question;