重新渲染默认保持旧状态

问题描述 投票:0回答:1

我有一个 React Typescript 应用程序,其中有一些问题对象。当我单击按钮时,我会打乱现有的 FilteredQuestions 状态数组,但列表渲染中的 Question 组件似乎不会重新渲染,因为旧状态被保留。

// App.tsx
import { useEffect, useRef, useState } from "react";
import "./App.css";
import Question from "./Question";
import React from "react";
import { shuffleQuestions } from "./utils/utils";
import { QuestionType } from "./types/types";

const SECTION_SIZE = 20;

function App() {
    const refs = useRef<Array<React.RefObject<HTMLDivElement>>>([]);
    const [scrollId, setScrollId] = useState<number>(-1);

    // Questions array is filled after a JSON file is parsed
    const [questions, setQuestions] = useState<QuestionType[]>(
        [] as QuestionType[]
    );
    const [numberOfSections, setNumberOfSections] = useState<number>(0);
    const [currentSection, setCurrentSection] = useState<number>(1);
    const [filteredQuestions, setFilteredQuestions] = useState<QuestionType[]>(
        []
    );
    const [searchQuestion, setSearchQuestion] = useState<string>("");


    useEffect(() => {
        refs.current = questions.map(() => React.createRef());
    }, [questions]);


    const handleChangeSection = (e: React.MouseEvent<HTMLButtonElement>) => {
        const sectionClicked = Number((e.target as HTMLButtonElement).value);

        if (currentSection === sectionClicked) {
            let arr = [...filteredQuestions];
            setFilteredQuestions(shuffleQuestions(arr));
        } else {
            setCurrentSection(sectionClicked);
        }
    };



    return (
        <>
            <div className='container'>
                <div
                    style={{
                        display: "flex",
                        flexDirection: "row",
                        alignItems: "center",
                        gap: "1rem",
                    }}
                >
                    <button
                        onClick={() => {
                            window.location.reload();
                        }}
                    >
                        Reset all
                    </button>
                    <label htmlFor='avatar'>
                        Choose a JSON file containig questions:
                    </label>
                    <input
                        type='file'
                        id='avatar'
                        name='avatar'
                        accept='application/JSON'
                        onChange={handleUploadFile}
                    />
                </div>

                <div style={{ display: "flex", flexWrap: "wrap" }}>
                    {Array.from({ length: numberOfSections }, (_, index) => (
                        <button onClick={handleChangeSection} key={index} value={index + 1}>
                            {index * SECTION_SIZE +
                                1 +
                                "-" +
                                Math.min(questions.length, (index + 1) * SECTION_SIZE)}
                        </button>
                    ))}
                </div>


                { filteredQuestions.map((question, index) => (
                            <div ref={refs.current[index]} key={question.id}>
                                <Question
                                    question={question}
                                    setScrollId={setScrollId}
                                    originalId={question.id}
                                    randomIdx={index}
                                />
                            </div>
                      ))
                }
            </div>
        </>
    );
}

export default App;
// Question.tsx

import { useEffect, useState } from "react";
import { QuestionType, ChoiceType } from "./types/types";

enum AnsweredQuestionState {
    CORRECT = "CORRECT",
    WRONG = "WRONG",
}

type AnsweredQuestion = {
    state: AnsweredQuestionState | null;
};

type QuestionProps = {
    question: QuestionType;
    setScrollId: React.Dispatch<React.SetStateAction<number>>;
    originalId: number;
    randomIdx: number;
};

export default function Question(props: QuestionProps) {
    const { question, setScrollId, originalId, randomIdx } = props;

    // If, for example, the new state is AnsweredQuestion.CORRECT, it still stays
    // the same, even though randomIdx have changed, for example. So the props
    // have changed, but the state is still the same

    
    const [selectedAnswer, setSelectedAnswer] = useState<AnsweredQuestion>({
        state: null,
    });

    const getQuestionStateStyle = (
        questionChoice: ChoiceType,
        choiceIdx: number
    ) => {
        if (choiceIdx == 0) {
            console.log(
                "Checking styles for question" + question.quest + " with randomIdx:",
                randomIdx
            );

            console.log(`Question ${question.quest} rendered`);
            console.log(selectedAnswer);
        }

        if (selectedAnswer.state) {
            if (questionChoice.isCorrect) {
                return "green";
            }

            if (selectedAnswer.state === AnsweredQuestionState.WRONG) {
                if (questionChoice.isCorrect === undefined) {
                    return "red";
                }
            }
        }
    };

    const handleAnswerClick = (choice: ChoiceType) => {
        setSelectedAnswer((prevSelectedAnswer) => {
            const state = choice.isCorrect
                ? AnsweredQuestionState.CORRECT
                : AnsweredQuestionState.WRONG;
            return { ...prevSelectedAnswer, state };
        });
    };


    return (
        <div className='question-container'>
            <h4>
                {originalId + 1}. {question.quest}
            </h4>
            {question.choices.map((questionChoice, idx) => (
                <div
                    className={`question ` + getQuestionStateStyle(questionChoice, idx)}
                    onClick={() => handleAnswerClick(questionChoice)}
                    key={idx}
                >
                    {questionChoice.str}
                </div>
            ))}
        </div>
    );
}
export type QuestionDefault = {
    quest: string;
    choices: ChoiceType[]
}
export type QuestionType = QuestionDefault & {
    id: number;
}

预期:列表渲染中的每个问题组件都将重新渲染,因为filteredQuestions变量已更改(由于随机化,甚至randomIdx也可能会被修改),因此oldState不应保留在问题组件中。

行为:问题组件似乎确实更新了道具,但旧状态(selectedAnswer)也被保留。如果我打印 randomIdx,如果此道具已更改,它会打印正确的,但 selectedAnswer 仍与以前相同(选择的样式与以前应用的样式相同)

reactjs typescript rendering react-props
1个回答
0
投票

您的问题在这里:

        <div style={{ display: "flex", flexWrap: "wrap" }}>
            {Array.from({ length: numberOfSections }, (_, index) => (
                <button onClick={handleChangeSection} key={index} value={index + 1}>
                    {index * SECTION_SIZE +
                        1 +
                        "-" +
                        Math.min(questions.length, (index + 1) * SECTION_SIZE)}
                </button>
            ))}
        </div>

您显示的元素数量等于

numberOfSections
。但
numberOfSections
始终是
0
。你不需要
numberOfSections
。您可以删除它。

重写此 HTML 以映射问题数组。帮自己一个忙,也将索引直接传递给

handleChangeSection

        <div style={{ display: 'flex', flexWrap: 'wrap' }}>
          {questions.map((_, index) => (
            <button onClick={() => handleChangeSection(index + 1)} key={index}>
              {index * SECTION_SIZE +
                1 +
                '-' +
                Math.min(questions.length, (index + 1) * SECTION_SIZE)}
            </button>
          ))}
        </div>

那么

handleChangeSection
将是:

  const handleChangeSection = (sectionClicked: number) => {
    if (currentSection === sectionClicked) {
      let arr = [...filteredQuestions];
      setFilteredQuestions(arr);
    } else {
      setCurrentSection(sectionClicked);
    }
  };
© www.soinside.com 2019 - 2024. All rights reserved.