我会尽我所能,非常具体地介绍我正在开发的应用程序和我现在面临的问题,首先,感谢您花时间阅读和帮助我!我目前正在开发一个语言评估应用程序,为此,我使用react js和Firestore来存储问题。我基本上是获取问题,将它们存储在父组件状态,并将问题传递给问题组件。一旦用户按下“下一步”按钮,会话存储上的计数器就会递增,我还会跟踪用户在会话存储上的进度,并访问一个不同的问题,让组件重新渲染。
这是完美的工作,正如预期的那样,然而,我现在的问题是,整个问题页面正在被重新呈现,我有一个进度条组件在页面顶部,不应该每次问题改变时都重新呈现,因为他的父状态没有改变。
这是我的代码:
TestQuestionsPage组件代码-父级
import { useEffect } from 'react';
import { ProgressBar } from '../../ProgressBar/ProgressBar';
import { QUESTION_TYPES } from '../../../utils/constants';
import QuestionStateHandler from '../../QuestionStateHandler/QuestionStateHandler';
export default function TestQuestionsPage() {
useEffect(() => {
console.log('re rendering');
return () => console.log('testQuestionPage unmounting');
}, []);
return (
<div>
<ProgressBar questionsType={QUESTION_TYPES.GRAMMAR} />
<QuestionStateHandler />
</div>
);
}
QuestionStateHandler组件——管理每个问题的状态更改的组件
import React, { useEffect, useState } from 'react';
import Question from '../Question/Question';
import { queryQuestions } from '../../utils/firebase-utils';
import { encryptData } from '../../utils/crypto-utils';
export default function QuestionStateHandler() {
const [testType, setTestType] = useState('grammarQuestions');
const [level, setLevel] = useState('A1');
//max questions will change based on the testType;
const [maxQuestionsPerLevel, setMaxQuestionsPerLevel] = useState(10);
const [setOfQuestions, setQuestions] = useState(null);
//state variable that will hold the user score
const [userScore, setUserScore] = useState(0);
//total number of questions
const [totalQuestions, setTotalQuestions] = useState(50);
useEffect(() => {
//if we don't have any questions
console.log('fetching questions');
queryQuestions(
testType,
['A1', 'A2', 'B1', 'B2', 'C1'],
maxQuestionsPerLevel,
).then((res) => {
console.log(res);
const encryptedQuestions = encryptData(res);
setTotalQuestions(res.length);
setQuestions(encryptedQuestions);
});
return () => console.log('unmounting component');
}, [testType]);
return (
<Question
totalQuestions={totalQuestions}
testType={testType}
setTestType={setTestType}
setOfQuestions={setOfQuestions && setOfQuestions}
/>
);
}
问题部分
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSessionStorage } from '../hooks/useSessionStorage';
import { QUESTION_TYPES } from '../../utils/constants';
import MediumButton from '../MediumButton/MediumButton';
import QuestionAttachment from '../QuestionAttachment/QuestionAttachment';
import './Question.css';
import {
decryptQuestion,
encryptData,
decryptData,
} from '../../utils/crypto-utils';
export default function Question({
totalQuestions,
testType,
setTestType,
setOfQuestions,
}) {
const [checkedOption, setCheckedOption] = useState(null);
const [isOptionSelected, setIsOptionSelected] = useState(false);
const [questionObj, setQuestionObj] = useState(null);
const [questionID, setQuestionID] = useState(null);
const [correctAnswer, setCorrectAnswer] = useState(null);
const [counter, setCounter] = useSessionStorage('counter', 0);
const [isLastQuestion, setLastQuestion] = useState(false);
// const { totalQuestions, testType, setTestType } = useAppContext();
const navigate = useNavigate();
// this function will be used to create the user progress object and track their correct answers,
function testTakerProgress(qID, isTheAnswerCorrect) {
//create a new object and encrypt it
const userProgressObj = {
question_level: questionObj.level,
question_id: questionID,
has_answered_correctly: isTheAnswerCorrect,
};
const userProgress = sessionStorage.getItem('user');
//if we have an user progress object already created
if (userProgress) {
let currentProgress = decryptData(userProgress);
currentProgress = [userProgressObj, ...currentProgress];
sessionStorage.setItem('user', encryptData(currentProgress));
console.log(currentProgress);
} else {
//we don't have an user progress created
const progressArray = [];
progressArray.push(userProgressObj);
sessionStorage.setItem('user', encryptData(progressArray));
console.log(progressArray);
}
}
useEffect(() => {
if (setOfQuestions) {
const q = decryptQuestion(counter, setOfQuestions);
console.log(q);
const qID = Object.keys(q);
setQuestionID(...qID);
setQuestionObj(q[qID]);
console.log(totalQuestions);
}
return () => {
setQuestionObj(null);
setQuestionID(null);
};
}, [setOfQuestions]);
useEffect(() => {
if (isLastQuestion === true) {
console.log('we are at the last question');
}
}, [isLastQuestion]);
function handleNext() {
//incrementing the question counter
setCounter((prevCount) => parseInt(prevCount) + 1);
if (checkedOption === correctAnswer) {
testTakerProgress(questionID, true);
} else {
testTakerProgress(questionID, false);
}
if (counter === totalQuestions - 1) {
setLastQuestion(true);
}
}
function handleSubmit() {
console.log('unmounting the question component');
//navigate to the test page, unmount the component
navigate('/');
}
return (
questionObj && (
<div className="pageContainer">
{testType === QUESTION_TYPES.LISTENING && (
<div
className={`${
testType === QUESTION_TYPES.GRAMMAR && 'disabled'
} questionAttachmentContainer`}
>
<QuestionAttachment
questionType={testType}
questionAttachmentTitle="title"
questionAttachmentBody={questionObj.mediaURL}
/>
</div>
)}
<div className="questionContainer">
<h4 className="questionInstruction">{questionObj.question}</h4>
{/* <p className="questionPrompt">{questionPrompt}</p> */}
<form className="formContainer">
{questionObj.options &&
questionObj.options.map((option, index) => (
<div
className={`optionContainer ${
checkedOption === index && 'activeLabel'
}`}
key={index}
>
<input
id={index}
type="radio"
checked={checkedOption === index}
onChange={() => {
setIsOptionSelected(true);
console.log(isOptionSelected);
setCheckedOption(index);
}}
/>
<label htmlFor={index}>{option}</label>
</div>
))}
<div className="buttonContainer">
<MediumButton
text={isLastQuestion ? 'Submit' : 'Next'}
onClick={isLastQuestion ? handleSubmit : handleNext}
disabled={isOptionSelected ? '' : 'disabled'}
/>
</div>
</form>
</div>
</div>
)
);
}
再次感谢您抽出时间!!我还附上了一个gif来向你展示这个漏洞。
Application Gif
我尝试了几件事和不同的方法,但到目前为止似乎没有任何效果。