1

Here is my problem: I'm making in Ract a clone of Google Forms. The structure of the data in the Redux store is as follows:

sections: [
  {
    sectionTitle: 'first section',
    questions: [
    {
    questionType: 'checkbox',
    question: 'some question',
    options: [
      {
        number: 1,
        text: 'sometimes',
      },
      ],
    },
    ],
  },
],

In the feature loading the main edig page, I render this questions in the following map:

{form.sections.map((section, index) => {
  return (
    <Box key={index}>
    <Box mb={5}>
      <SectionCard index={index} />
    </Box>
    
  {section.questions.map((question, questionIndex) => {
    return (
      <QuestionCard                                         
      key={questionIndex}                       
      sectionIndex={index}                          
      questionIndex={questionIndex}
       />
     );
   })}
    </Box>
 );
})}

Each component picks the values from the store with useSelector, like the question description, the options, question type.

QuestionCard:

const QuestionCard = ({ sectionIndex, questionIndex }) => {
    const question = useSelector(
        state => state.form.sections[sectionIndex].questions[questionIndex]
    );
    const [hasQuestionChanged, setHasQuestionChanged] = useState(false);
    const dispatch = useDispatch();

    const handleChangeQuestionValue = e => {
        dispatch(
            setQuestionDescription({
                sectionNumber: sectionIndex,
                questionNumber: questionIndex,
                newValue: e.target.value,
            })
        );
        setHasQuestionChanged(true);
    };
return(
...
                <CardContent>
                    <TextField
                        multiline
                        label={`Question ${questionIndex + 1}`}
                        value={question.question}
                        margin='normal'
                        variant='standard'
                        onChange={handleChangeQuestionValue}
                        fullWidth
                    />
                    {question.questionType === 'multiple' &&
                        question.options.map((option, index) => {
                            return (
                                <RadioQuestion
                                    key={index}
                                    sectionIndex={sectionIndex}
                                    questionIndex={questionIndex}
                                    optionIndex={index}
                                />
                            );
                        })}
...

)

RadioQuestion


const RadioQuestion = memo(({ sectionIndex, questionIndex, optionIndex }) => {
    const theme = useTheme();
    const dispatch = useDispatch();
    const [selected, setSelected] = useState(false);
    const option = useSelector(
        state =>
            state.form.sections[sectionIndex].questions[questionIndex].options[
                optionIndex
            ]
    );

    const handleChangeOptionDescription = e => {
        dispatch(
            setQuestionOptionDescription({
                sectionIndex: sectionIndex,
                questionIndex: questionIndex,
                optionIndex: optionIndex,
                description: e.target.value,
            })
        );
    // Update the state
    const handleChangeOptionValue = e => {
        setSelected(!selected);
    };
return(
...
    <FormControl mt='1rem'>
                <RadioGroup>
                    <Box justifyContent='center' sx={{ alignItems: 'center' }}>
                        <FormControlLabel
                            labelPlacement='top'
                            value={option.number}
                            control={<Radio />}
                            checked={selected}
                            sx={{ mt: 3 }}
                            onClick={handleChangeOptionValue}
                        />
                        <FormControl>
                            <TextField
                                fullWidth
                                label={`Option ${option.number}`}
                                value={option.text ? option.text : ''}
                                variant='standard'
                                margin='normal'
                                onChange={handleChangeOptionDescription}
                            />
                        </FormControl>
                    </Box>
                </RadioGroup>
            </FormControl>
...
)

SectionCard

const SectionCard = ({ index }) => {
    const isMobile = useMediaQuery('(max-width: 400px)');
    const theme = useTheme();
    const dispatch = useDispatch();
    const [errorTitle, setErrorTitle] = useState();
    const { sectionTitle } = useSelector(state => state.form.sections[index]);
    const totalSections = useSelector(
        state => state.form.computedSections.computed.totalSections
    );

    const handleChangeSectionTitle = e => {
        dispatch(
            changeSectionTitle({ sectionIndex: index, newValue: e.target.value })
        );
    };

    return (
...
            <FormControl>
                    <TextField
                        required
                        autoComplete='off'
                        sx={{ minWidth: isMobile ? 150 : 400 }}
                        label='Section Title'
                        variant='standard'
                        value={sectionTitle}
                        margin='normal'
                        color={theme.palette.secondary[100]}
                        onChange={handleChangeSectionTitle}
                        error={errorTitle}
                    />
                    {errorTitle && (
                        <FormHelperText error>
                            The Section Title can't be empty
                        </FormHelperText>
                    )}
                </FormControl>
...
)

The problem is that when I edit a question filed and dispatch the value it rerenders all the question cards, which makes typing a bit slow. If I try to memoize the QuestionCard or the SectionCard I get a TypeError:

const SectionCard = memo(({ index }) => {...})
>Uncaught TypeError: Component is not a function

How the component looks

As you can see, every time I change a value, 20 rerenders happen, so I need to only rerender que component that has changed and I can't find the way to do it. I believe that the root problem is that the state is mapping through the sections and questions, and returning a new state, making all the components rerender.

As another guess on the problem and its solution, I understand that I shoul use useSelector to memoize, but I am really lost since the data structured is so nested.

Thanks in advance

I tried memoizing the function that renders the Section and the Question, which gives an error, I tried to useMemo on the parent component ant render the memoized component instead of the original. I am not sure on how to handle to render only when that specific component has changed.

0 Answers0