代码之家  ›  专栏  ›  技术社区  ›  Rodrigo Gil

React JS:更新父组件时子组件的状态更改

  •  0
  • Rodrigo Gil  · 技术社区  · 2 年前

    我会尽我所能,非常具体地介绍我正在开发的应用程序和我现在面临的问题,首先,感谢您花时间阅读和帮助我!我目前正在开发一个语言评估应用程序,为此,我使用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

    我尝试了几件事和不同的方法,但到目前为止似乎没有任何效果。

    1 回复  |  直到 2 年前
        1
  •  2
  •   Wings    2 年前

    从gif来看,整个页面似乎都在重新加载,但我不确定MediumButton组件中有什么。默认情况下,表单元素中的按钮将在单击时提交表单,您需要在button onClick处理程序中实现preventDefault,下面是一个示例:

    function App() {
      const handleClick = (e) => {
        e.preventDefault();
      }
      return (
        <div className="App">
    
          <form>
            <p>This is simple form</p>
            <button onClick={(e) => handleClick(e)}>hello</button>
          </form>
        </div>
        )
      };