当输入更改时,意外的 React 倒计时组件重新渲染

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

我使用 react-countdown 包创建了一个自定义倒计时组件。 一般来说,它工作得很好。但是,当我在页面中的文本输入中键入内容时,它会以某种方式再次呈现并重置为初始时间。我已经检查了输入的 onChange 事件,但它与倒计时无关。我真的很困惑为什么会发生这种情况。

我创建倒计时的想法是,如果我更改倒计时组件的 key 属性,我将有一个新的倒计时。因为据我所知,如果我们更改反应组件中的关键道具,它们将重新渲染。

倒计时组件:

const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
  const classes = useStyles();
  const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
    if (completed) {
      return children;
    }

    return (
      <span className={classes.root}>
        {minutes}:{seconds}
      </span>
    );
  };
  return (
    <Countdown
      renderer={defaultRenderer}
      date={Date.now() + duration}
      key={restartKey}
      {...props}
    />
  );
};

用途:

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

同一页面中的输入元素:

<AgapeTextField
            placeholder="مثال: ۱۲۳۴۵"
            variant="outlined"
            fullWidth
            onChange={handleOtpChange}
            value={otp}
            helperText={otpHelperText}
            error={otpHelperText}
          />

输入更改处理程序:

const handleOtpChange = (event) => {
  if (otpRegex.test(event.target.value)) {
    setOtpHelperText(null);
    setDisableOtpAction(false);
    setOtp(event.target.value).then(() => {
      nextButtonClicked();
    });
  } else {
    setOtp(event.target.value);
    setOtpHelperText(helperInvalidOtp);
    setDisableOtpAction(true);
  }
};

countdownKey 更新的位置:

const handleResendOtpClick = () => {
  setCountdownKey(countdownKey + 1);
  console.log('hello from resendotpclick');
  registerApiService({
    mobile: phoneNumberPure,
  })
    .then((response) => {
      if (response.status === 200) {
        // TODO show user that otp code resent.
      }
    })
    .catch((error) => {
      // TODO show user that otp code resend failed.
    });
};

用于更深入检查的完整代码:

const LoginStep2 = ({ dialogHandler, ...props }) => {
  const classes = useStyles(props);
  const setIsLoginOpen = dialogHandler;
  const dispatch = useDispatch();
  const phoneNumberPure = useSelector(selectPhone);
  const ELogin = useSelector(selectELogin);
  const [otp, setOtp] = useStateWithPromise(null);
  const [otpHelperText, setOtpHelperText] = React.useState(null);
  const [disableOtpAction, setDisableOtpAction] = React.useState(true);
  const [phoneNumber, setPhoneNumber] = React.useState('');
  const [countdownKey, setCountdownKey] = React.useState(1);

  React.useEffect(() => {
    if (phoneNumberPure) {
      setPhoneNumber(phoneNumberPure.split('-')[1]);
    }
  }, [phoneNumberPure]);

  const handlePrevIconClicked = () => {
    if (ELogin) {
      dispatch(next());
      return;
    }
    dispatch(prev());
  };

  const nextButtonClicked = () => {
    setDisableOtpAction(true);
    const convertedOtp = convertPersianDigitsToEnglish(otp);
    loginApiService({ mobile: phoneNumberPure, otp: convertedOtp })
      .then((response) => {
        if (response.status === 200) {
          if (response.data.access_token) {
            const jsonUser = {
              phone: phoneNumberPure,
              token: response.data.access_token,
              social: null,
              email: null,
            };
            localStorage.setItem('user', JSON.stringify(jsonUser));
            if (ELogin) {
              setIsLoginOpen(false);
              return;
            }
            dispatch(next());
          }
        } else if (response.status === 404) {
          setOtpHelperText(helperWrongOtp);
        }
      })
      .catch((error) => {
        setOtpHelperText(helperWrongOtp);
      })
      .finally(() => {
        setTimeout(() => {
          setDisableOtpAction(false);
        }, 1000);
      });
  };

  const handleResendOtpClick = () => {
    setCountdownKey(countdownKey + 1);
    console.log('hello from resendotpclick');
    registerApiService({
      mobile: phoneNumberPure,
    })
      .then((response) => {
        if (response.status === 200) {
          // TODO show user that otp code resent.
        }
      })
      .catch((error) => {
        // TODO show user that otp code resend failed.
      });
  };

  const handleOtpChange = (event) => {
    if (otpRegex.test(event.target.value)) {
      setOtpHelperText(null);
      setDisableOtpAction(false);
      setOtp(event.target.value).then(() => {
        nextButtonClicked();
      });
    } else {
      setOtp(event.target.value);
      setOtpHelperText(helperInvalidOtp);
      setDisableOtpAction(true);
    }
  };
  return (
    <Grid container>
      <Grid item xs={12}>
        <IconButton onClick={handlePrevIconClicked}>
          <BsArrowRight className={classes.arrowIcon} />
        </IconButton>
      </Grid>
      <Grid
        item
        container
        xs={12}
        justify="center"
        className={classes.logoContainer}
      >
        <img src={AgapeLogo} alt="لوگوی آگاپه" />
      </Grid>
      <Grid
        item
        container
        xs={12}
        justify="center"
        className={classes.loginTitle}
      >
        <Typography variant="h4">کد تایید را وارد نمایید</Typography>
      </Grid>
      <Grid item xs={12}>
        <Typography variant="body1" className={classes.noMargin}>
          کد تایید به شماره
          <span className={classes.phoneNumberContainer}>{phoneNumber}</span>
          ارسال گردید
        </Typography>
      </Grid>
      <Grid
        item
        container
        xs={12}
        justify="space-between"
        className={classes.loginInputs}
      >
        <Grid item xs={12}>
          <AgapeTextField
            placeholder="مثال: ۱۲۳۴۵"
            variant="outlined"
            fullWidth
            onChange={handleOtpChange}
            value={otp}
            helperText={otpHelperText}
            error={otpHelperText}
          />
        </Grid>
      </Grid>
      <Grid item xs={12}>
        <AgapeButton
          color="primary"
          disabled={disableOtpAction}
          onClick={nextButtonClicked}
          fullWidth
        >
          تایید
        </AgapeButton>
      </Grid>

      <Grid
        item
        container
        xs={12}
        justify="space-between"
        className={classes.textButtonsContainer}
      >
        <Grid item xs={4}>
          <AgapeCountdown duration={10000} restartKey={countdownKey}>
            <AgapeButton
              onClick={handleResendOtpClick}
              className={classes.textButton}
            >
              ارسال مجدد کد
            </AgapeButton>
          </AgapeCountdown>
        </Grid>
        <Grid item xs={4} className={classes.callButton}>
          <AgapeButton className={classes.textButton}>
            دریافت از طریق تماس
          </AgapeButton>
        </Grid>
      </Grid>
    </Grid>
  );
};
reactjs countdown
2个回答
1
投票

我发现问题了。这实际上是造成问题的部分:

<Countdown
      renderer={defaultRenderer}
      date={Date.now() + duration}
      key={restartKey}
      {...props}
    />

Date.now()
将会更新。并且它使重新启动倒计时。为了解决这个问题,我使用了
ref
来阻止组件在发生变化时重新渲染:

const AgapeCountdown = ({ duration, children, restartKey, ...props }) => {
  const classes = useStyles();
  const startDate = React.useRef(Date.now());
  const defaultRenderer = ({ hours, minutes, seconds, completed }) => {
    return (
      <span className={classes.root}>
        {minutes}:{seconds}
      </span>
    );
  };
  return (
    <Countdown
      renderer={defaultRenderer}
      date={startDate.current + duration}
      key={restartKey}
      {...props}
    />
  );
};

0
投票

这有效!

基本上,在重新渲染时,Date.now() 将捕获新时间(重新渲染的时间),而不是“记住”倒计时开始的初始时间。

通过将其锁定在 useRef 中,即使在重新渲染之后,ref 中的值也不会改变。

© www.soinside.com 2019 - 2024. All rights reserved.