reactjs 当输入更改时,意外的React倒计时组件重新呈现

wwwo4jvm  于 2023-10-17  发布在  React
关注(0)|答案(2)|浏览(167)

我已经创建了一个自定义的倒计时组件使用react-countdown包。一般来说,它工作得很好。但是当我在页面中输入文本时,它会以某种方式再次呈现并重置为初始时间。我已经检查了输入的onChange事件,但它与倒计时无关。我真的很困惑为什么会这样。
我的想法是在创造倒计时是,如果我改变了关键 prop 的倒计时组件,我将有一个新的倒计时。因为我知道如果我们改变了react组件中的关键 prop ,它们会重新渲染。
倒计时组件:

  1. const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
  2. const classes = useStyles();
  3. const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
  4. if (completed) {
  5. return children;
  6. }
  7. return (
  8. <span className={classes.root}>
  9. {minutes}:{seconds}
  10. </span>
  11. );
  12. };
  13. return (
  14. <Countdown
  15. renderer={defaultRenderer}
  16. date={Date.now() + duration}
  17. key={restartKey}
  18. {...props}
  19. />
  20. );
  21. };

使用方法:

  1. <AgapeCountdown duration={10000} restartKey={countdownKey}>
  2. <AgapeButton onClick={handleResendOtpClick} className={classes.textButton}>
  3. ارسال مجدد کد
  4. </AgapeButton>
  5. </AgapeCountdown>;

在同一页中输入元素:

  1. <AgapeTextField
  2. placeholder="مثال: ۱۲۳۴۵"
  3. variant="outlined"
  4. fullWidth
  5. onChange={handleOtpChange}
  6. value={otp}
  7. helperText={otpHelperText}
  8. error={otpHelperText}
  9. />

输入更改处理程序:

  1. const handleOtpChange = (event) => {
  2. if (otpRegex.test(event.target.value)) {
  3. setOtpHelperText(null);
  4. setDisableOtpAction(false);
  5. setOtp(event.target.value).then(() => {
  6. nextButtonClicked();
  7. });
  8. } else {
  9. setOtp(event.target.value);
  10. setOtpHelperText(helperInvalidOtp);
  11. setDisableOtpAction(true);
  12. }
  13. };

其中countdownKey得到更新:

  1. const handleResendOtpClick = () => {
  2. setCountdownKey(countdownKey + 1);
  3. console.log('hello from resendotpclick');
  4. registerApiService({
  5. mobile: phoneNumberPure,
  6. })
  7. .then((response) => {
  8. if (response.status === 200) {
  9. // TODO show user that otp code resent.
  10. }
  11. })
  12. .catch((error) => {
  13. // TODO show user that otp code resend failed.
  14. });
  15. };

完整的代码进行更深入的检查:

  1. const LoginStep2 = ({ dialogHandler, ...props }) => {
  2. const classes = useStyles(props);
  3. const setIsLoginOpen = dialogHandler;
  4. const dispatch = useDispatch();
  5. const phoneNumberPure = useSelector(selectPhone);
  6. const ELogin = useSelector(selectELogin);
  7. const [otp, setOtp] = useStateWithPromise(null);
  8. const [otpHelperText, setOtpHelperText] = React.useState(null);
  9. const [disableOtpAction, setDisableOtpAction] = React.useState(true);
  10. const [phoneNumber, setPhoneNumber] = React.useState('');
  11. const [countdownKey, setCountdownKey] = React.useState(1);
  12. React.useEffect(() => {
  13. if (phoneNumberPure) {
  14. setPhoneNumber(phoneNumberPure.split('-')[1]);
  15. }
  16. }, [phoneNumberPure]);
  17. const handlePrevIconClicked = () => {
  18. if (ELogin) {
  19. dispatch(next());
  20. return;
  21. }
  22. dispatch(prev());
  23. };
  24. const nextButtonClicked = () => {
  25. setDisableOtpAction(true);
  26. const convertedOtp = convertPersianDigitsToEnglish(otp);
  27. loginApiService({ mobile: phoneNumberPure, otp: convertedOtp })
  28. .then((response) => {
  29. if (response.status === 200) {
  30. if (response.data.access_token) {
  31. const jsonUser = {
  32. phone: phoneNumberPure,
  33. token: response.data.access_token,
  34. social: null,
  35. email: null,
  36. };
  37. localStorage.setItem('user', JSON.stringify(jsonUser));
  38. if (ELogin) {
  39. setIsLoginOpen(false);
  40. return;
  41. }
  42. dispatch(next());
  43. }
  44. } else if (response.status === 404) {
  45. setOtpHelperText(helperWrongOtp);
  46. }
  47. })
  48. .catch((error) => {
  49. setOtpHelperText(helperWrongOtp);
  50. })
  51. .finally(() => {
  52. setTimeout(() => {
  53. setDisableOtpAction(false);
  54. }, 1000);
  55. });
  56. };
  57. const handleResendOtpClick = () => {
  58. setCountdownKey(countdownKey + 1);
  59. console.log('hello from resendotpclick');
  60. registerApiService({
  61. mobile: phoneNumberPure,
  62. })
  63. .then((response) => {
  64. if (response.status === 200) {
  65. // TODO show user that otp code resent.
  66. }
  67. })
  68. .catch((error) => {
  69. // TODO show user that otp code resend failed.
  70. });
  71. };
  72. const handleOtpChange = (event) => {
  73. if (otpRegex.test(event.target.value)) {
  74. setOtpHelperText(null);
  75. setDisableOtpAction(false);
  76. setOtp(event.target.value).then(() => {
  77. nextButtonClicked();
  78. });
  79. } else {
  80. setOtp(event.target.value);
  81. setOtpHelperText(helperInvalidOtp);
  82. setDisableOtpAction(true);
  83. }
  84. };
  85. return (
  86. <Grid container>
  87. <Grid item xs={12}>
  88. <IconButton onClick={handlePrevIconClicked}>
  89. <BsArrowRight className={classes.arrowIcon} />
  90. </IconButton>
  91. </Grid>
  92. <Grid
  93. item
  94. container
  95. xs={12}
  96. justify="center"
  97. className={classes.logoContainer}
  98. >
  99. <img src={AgapeLogo} alt="لوگوی آگاپه" />
  100. </Grid>
  101. <Grid
  102. item
  103. container
  104. xs={12}
  105. justify="center"
  106. className={classes.loginTitle}
  107. >
  108. <Typography variant="h4">کد تایید را وارد نمایید</Typography>
  109. </Grid>
  110. <Grid item xs={12}>
  111. <Typography variant="body1" className={classes.noMargin}>
  112. کد تایید به شماره
  113. <span className={classes.phoneNumberContainer}>{phoneNumber}</span>
  114. ارسال گردید
  115. </Typography>
  116. </Grid>
  117. <Grid
  118. item
  119. container
  120. xs={12}
  121. justify="space-between"
  122. className={classes.loginInputs}
  123. >
  124. <Grid item xs={12}>
  125. <AgapeTextField
  126. placeholder="مثال: ۱۲۳۴۵"
  127. variant="outlined"
  128. fullWidth
  129. onChange={handleOtpChange}
  130. value={otp}
  131. helperText={otpHelperText}
  132. error={otpHelperText}
  133. />
  134. </Grid>
  135. </Grid>
  136. <Grid item xs={12}>
  137. <AgapeButton
  138. color="primary"
  139. disabled={disableOtpAction}
  140. onClick={nextButtonClicked}
  141. fullWidth
  142. >
  143. تایید
  144. </AgapeButton>
  145. </Grid>
  146. <Grid
  147. item
  148. container
  149. xs={12}
  150. justify="space-between"
  151. className={classes.textButtonsContainer}
  152. >
  153. <Grid item xs={4}>
  154. <AgapeCountdown duration={10000} restartKey={countdownKey}>
  155. <AgapeButton
  156. onClick={handleResendOtpClick}
  157. className={classes.textButton}
  158. >
  159. ارسال مجدد کد
  160. </AgapeButton>
  161. </AgapeCountdown>
  162. </Grid>
  163. <Grid item xs={4} className={classes.callButton}>
  164. <AgapeButton className={classes.textButton}>
  165. دریافت از طریق تماس
  166. </AgapeButton>
  167. </Grid>
  168. </Grid>
  169. </Grid>
  170. );
  171. };
r6hnlfcb

r6hnlfcb1#

我发现问题了。这是真正产生问题的部分:

  1. <Countdown
  2. renderer={defaultRenderer}
  3. date={Date.now() + duration}
  4. key={restartKey}
  5. {...props}
  6. />

Date.now()将更新。开始倒计时。为了解决这个问题,我使用了一个ref,如果组件发生变化,它会停止重新渲染:

  1. const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
  2. const classes = useStyles();
  3. const startDate = React.useRef(Date.now());
  4. const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
  5. return (
  6. <span className={classes.root}>
  7. {minutes}:{seconds}
  8. </span>
  9. );
  10. };
  11. return (
  12. <Countdown
  13. renderer={defaultRenderer}
  14. date={startDate.current + duration}
  15. key={restartKey}
  16. {...props}
  17. />
  18. );
  19. };
展开查看全部
yrdbyhpb

yrdbyhpb2#

成功了!
基本上,在重新呈现时,Date.now()将捕获一个新的时间(重新呈现的时间),而不是“记住”倒计时开始的初始时间。
通过将其锁定在useRef中,即使在重新渲染之后,ref中的值也不会更改。

相关问题