我有一个 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 仍与以前相同(选择的样式与以前应用的样式相同)
您的问题在这里:
<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);
}
};