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
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.