I'm making a form in react native, in which questions can branch to other questions, but every time I select an alternative or a checkbox it takes a long time for the selection animation to happen, it also takes a long time to find a branch
/* eslint-disable no-negated-condition */
import { HStack, Skeleton, Text, VStack, View } from 'native-base';
import { IconButton } from 'presentation/atomic/atoms/IconButton';
import { useEffect, useState } from 'react';
import type { FC } from 'react';
// eslint-disable-next-line import/no-cycle
import { QuestionInput } from 'presentation/atomic/molecules';
import { Table } from 'presentation/atomic/organisms/Table';
import { setAnsweredSections } from 'data/store/EvaluationQuiz';
import { useAppSelector } from 'data/store';
import { useDispatch } from 'react-redux';
import type { EvaluationQuizAnswer } from 'domain/models/evaluationQuizAnswer';
import type { EvaluationQuizChildren } from 'domain/models/evaluationQuizChildren';
interface SectionProps {
section: EvaluationQuizChildren;
isLoading?: boolean;
}
interface EvaluationQuizQuestionAnswer {
id: string;
answers: Omit<
EvaluationQuizAnswer,
'order' | 'ramificationId' | 'selected' | 'type'
>[];
}
export const Section: FC<SectionProps> = ({ section, isLoading }) => {
const [answeredQuestions, setAnsweredQuestions] = useState<
EvaluationQuizQuestionAnswer[]
>([]);
const { answeredSections } = useAppSelector((state) => state.evaluationQuiz);
const disptach = useDispatch();
const handleAnswers = (answer: EvaluationQuizQuestionAnswer): void => {
setAnsweredQuestions((prevAnswers) => {
const existingAnswerIndex = prevAnswers.findIndex(
(questionAnswer) => questionAnswer.id === answer.id
);
const updatedAnswers = [...prevAnswers];
if (existingAnswerIndex !== -1)
updatedAnswers[existingAnswerIndex] = answer;
else updatedAnswers.push(answer);
disptach(
setAnsweredSections({
id: section.id,
questions: updatedAnswers,
referenceTable: {
rows: [{ id: '', selectedColum: '' }]
}
})
);
return updatedAnswers;
});
};
useEffect(() => {
console.log(JSON.stringify(answeredSections));
}, [answeredSections]);
return (
<View>
{isLoading ? (
<VStack space={2} w={'full'}>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
h={4}
startColor={'local.gray'}
w={'30%'}
/>
<HStack justifyContent={'space-between'} w={'full'}>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
h={8}
startColor={'local.gray'}
w={'60%'}
/>
<HStack space={2}>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
size={8}
startColor={'local.gray'}
/>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
size={8}
startColor={'local.gray'}
/>
</HStack>
</HStack>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
h={2}
startColor={'local.gray'}
w={'80%'}
/>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
h={2}
startColor={'local.gray'}
w={'75%'}
/>
<Skeleton
borderRadius={'md'}
endColor={'gray.300'}
h={20}
startColor={'local.gray'}
w={'100%'}
/>
</VStack>
) : (
<VStack space={2}>
<Text bold color={'local.darkGray'} fontSize={'lg'} marginTop={4}>
{section?.name}
</Text>
{section?.referenceTable ? (
<Table key={section?.referenceTable?.name} />
) : null}
{section?.questions?.map((question, questionIndex) => (
// eslint-disable-next-line react/no-array-index-key
<VStack key={questionIndex} space={2}>
<HStack
alignItems={'flex-start'}
justifyContent={'space-between'}
mt={2}
>
<VStack flex={3}>
<Text
color={'local.darkGray'}
flexWrap={'wrap'}
fontSize={'md'}
fontWeight={'bold'}
>
{question?.name}
</Text>
<Text
color={'local.darkGray'}
flex={3}
flexWrap={'wrap'}
fontSize={'sm'}
>
{question?.statement}
</Text>
</VStack>
<HStack flex={1}>
<IconButton color={'black'} name={'chat-bubble-outline'} />
<IconButton color={'black'} name={'info'} />
</HStack>
</HStack>
<QuestionInput
onChangeQuestion={(value: EvaluationQuizQuestionAnswer): void =>
handleAnswers(value)
}
question={question}
/>
</VStack>
))}
</VStack>
)}
</View>
);
};
import { Input } from 'presentation/atomic/atoms';
import { QuestionTypeEnum } from 'domain/models/enums';
import { Radio, Text, TextArea, VStack, View } from 'native-base';
// eslint-disable-next-line import/no-cycle
import { Checkbox } from 'presentation/atomic/organisms/MultipleChoice';
// eslint-disable-next-line import/no-cycle
import { Section } from 'presentation/atomic/organisms';
import {
currencyMask,
dateFormat,
formatCNPJ,
phoneMask,
proposalFormat
} from 'main/utils/regex/cellphone';
import { useAppSelector } from 'data/store';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { EvaluationQuizAnswer } from 'domain/models/evaluationQuizAnswer';
import type { EvaluationQuizChildren } from 'domain/models/evaluationQuizChildren';
import type { EvaluationQuizQuestion } from 'domain/models/evaluationQuizQuestion';
import type { FC } from 'react';
interface QuestionInputProps {
question: EvaluationQuizQuestion;
onChangeQuestion: (value: {
id: string;
answers: Omit<
EvaluationQuizAnswer,
'order' | 'ramificationId' | 'selected' | 'type'
>[];
}) => void;
}
export const QuestionInput: FC<QuestionInputProps> = ({
question,
onChangeQuestion
}) => {
const questionType = QuestionTypeEnum;
const { childs } = useAppSelector((state) => state.evaluationQuiz);
const [ramification, setRamification] =
useState<EvaluationQuizChildren | null>(null);
const [choicesRamifications, setChoicesRamifications] = useState<string[]>(
[]
);
const [choices, setChoices] = useState<string[]>([]);
const handleRamifications = useCallback(
(selectedRamification: string, choiceId: string) => {
setChoices((prevChoices) => {
if (prevChoices.includes(choiceId))
return prevChoices.filter((choice) => choice !== choiceId);
return [...prevChoices, choiceId];
});
if (selectedRamification === null) return;
setChoicesRamifications((prevChoicesRamifications) => {
if (prevChoicesRamifications.includes(selectedRamification))
return prevChoicesRamifications.filter(
(prevRamification) => prevRamification !== selectedRamification
);
return [...prevChoicesRamifications, selectedRamification];
});
},
[]
);
useEffect(() => {
const updatedAnswers = choices.map((choice) => ({
id: choice,
title: ''
}));
onChangeQuestion({
answers: updatedAnswers,
id: question.id
});
}, [choices]);
const renderInput = useMemo(() => {
switch (question.type) {
case questionType.ALTERNATIVE:
return (
<Radio.Group
accessibilityLabel={question.name}
alignItems={'center'}
name={question.name}
onChange={(value): void => {
onChangeQuestion({
answers: [
{
id: value,
title: ''
}
],
id: question.id
});
const alternative = question.answers.find(
(answer) => answer.id === value
);
if (alternative?.ramificationId)
setRamification(
childs[
alternative?.ramificationId
] as unknown as EvaluationQuizChildren
);
else setRamification(null);
}}
>
{question.answers?.map((alternative) => (
<Radio
key={alternative.id}
colorScheme={'yellow'}
my={2}
value={alternative.id}
>
<Text
color={'local.darkGray'}
fontSize={'md'}
fontWeight={'medium'}
w={'90%'}
>
{alternative.title}
</Text>
</Radio>
))}
</Radio.Group>
);
case questionType.DISSERTATION:
return (
<TextArea
autoCompleteType={undefined}
color={'local.darkGray'}
fontSize={'md'}
fontWeight={'medium'}
mb={2}
minH={'24'}
multiline
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
p={2}
placeholder={'Digite aqui sua resposta'}
placeholderTextColor={'local.gray'}
variant={'underlined'}
w={'full'}
/>
);
case questionType.PERCENTAGE:
return (
<Input
InputRightElement={
<Text color={'black'} fontWeight={'bold'} pr={4}>
%
</Text>
}
keyboardType={'numeric'}
my={2}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Digite a porcentagem'}
/>
);
case questionType.MONETARY:
return (
<Input
keyboardType={'numeric'}
leftElement={
<Text color={'black'} fontWeight={'bold'} pl={4}>
R$
</Text>
}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Digite aqui o valor'}
value={currencyMask('119760,89')}
/>
);
case questionType.MULTIPLE_CHOICE:
return (
<VStack space={4}>
{question.answers.map((alternative) => (
<Checkbox
key={alternative.id}
alternative={alternative}
checked={choicesRamifications.includes(alternative?.id)}
onChange={(value: {
choiceId: string;
ramificationId: string;
}): void => {
handleRamifications(value.ramificationId, value.choiceId);
}}
/>
))}
</VStack>
);
case questionType.PHONE:
return (
<Input
keyboardType={'phone-pad'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Digite aqui o número do celular'}
value={phoneMask('11976057989')}
/>
);
case questionType.CNPJ:
return (
<Input
keyboardType={'numeric'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Enter CNPJ'}
value={formatCNPJ('12354687965254')}
/>
);
case questionType.DATE:
return (
<Input
keyboardType={'numeric'}
maxLength={8}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'31/12/9999'}
value={dateFormat('31122004')}
/>
);
case questionType.PROPOSAL:
return (
<Input
keyboardType={'numeric'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Digite um número da proposta'}
value={proposalFormat('342423423412')}
/>
);
case questionType.SIGNED_DECIMAL:
return (
<Input
keyboardType={'numeric'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
value={'12354687965254'}
/>
);
case questionType.SIGNED_INTEGER:
return (
<Input
keyboardType={'numeric'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
value={formatCNPJ('12354687965254')}
/>
);
case questionType.UNSIGNED_DECIMAL:
return (
<Input
keyboardType={'numeric'}
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
value={formatCNPJ('12354687965254')}
/>
);
case questionType.UNSIGNED_INTEGER:
return (
<Input
onChangeText={(value): void =>
onChangeQuestion({
answers: [{ id: '', title: value }],
id: question.id
})
}
placeholder={'Enter CNPJ'}
value={formatCNPJ('12354687965254')}
/>
);
default:
return <View />;
}
}, [questionType, choicesRamifications, question.answers]);
return (
<View>
{renderInput}
{ramification ? (
<VStack w={'full'}>
<Section section={ramification} />
</VStack>
) : null}
{choicesRamifications?.map((choiceRamification) => (
<View key={choiceRamification}>
{choiceRamification ? (
<VStack w={'full'}>
<Section section={childs[choiceRamification]} />
</VStack>
) : null}
</View>
))}
</View>
);
};
I tried to refactor my code in several ways but none of them worked, I would like a way to stay lighter