How can I improve form performance?

46 views Asked by At

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

0

There are 0 answers