Repository: logto-io/logto
File: packages/account-center/src/components/VerificationMethodList/index.tsx (TypeScript)

Code before:

const isVerificationMethod = (value: VerificationMethod | false): value is VerificationMethod =>
  value !== false;

const VerificationMethodList = () => {
  const { userInfo } = useContext(PageContext);
  const [verifyingMethod, setVerifyingMethod] = useState<VerificationMethod>();

  const hasPasswordVerification = Boolean(userInfo?.hasPassword);
  const hasEmailVerification = Boolean(userInfo?.primaryEmail);
  const hasPhoneVerification = Boolean(userInfo?.primaryPhone);

  const availableMethods = useMemo(
    () =>
      [
        hasPasswordVerification && VerificationMethod.Password,
        hasEmailVerification && VerificationMethod.EmailCode,
        hasPhoneVerification && VerificationMethod.PhoneCode,
      ].filter((method) => isVerificationMethod(method)),
    [hasEmailVerification, hasPasswordVerification, hasPhoneVerification]
  );

  useEffect(() => {
    if (!verifyingMethod && availableMethods.length === 1) {
      setVerifyingMethod(availableMethods[0]);
    }
  }, [availableMethods, verifyingMethod]);

  if (verifyingMethod === VerificationMethod.Password) {
    return (
      <PasswordVerification
        onBack={() => {
          setVerifyingMethod(undefined);
        }}
      />
    );
  }

  if (verifyingMethod === VerificationMethod.EmailCode) {
    return (
      <EmailVerification
        onBack={() => {
          setVerifyingMethod(undefined);
        }}
      />
    );
  }

  if (verifyingMethod === VerificationMethod.PhoneCode) {
    return (
      <PhoneVerification

Review comment (suggestion):
Maybe we should also `setVerifyingMethod(undefined)` with an `else` clause

Code after:

const isVerificationMethod = (value: VerificationMethod | false): value is VerificationMethod =>
  value !== false;

const VerificationMethodList = () => {
  const { userInfo } = useContext(PageContext);
  const [verifyingMethod, setVerifyingMethod] = useState<VerificationMethod>();
  // Track if we already auto-selected the only available method to avoid re-trigger on Back.
  const hasAutoSelectedMethod = useRef(false);

  const hasPasswordVerification = Boolean(userInfo?.hasPassword);
  const hasEmailVerification = Boolean(userInfo?.primaryEmail);
  const hasPhoneVerification = Boolean(userInfo?.primaryPhone);

  const availableMethods = useMemo(
    () =>
      [
        hasPasswordVerification && VerificationMethod.Password,
        hasEmailVerification && VerificationMethod.EmailCode,
        hasPhoneVerification && VerificationMethod.PhoneCode,
      ].filter((method) => isVerificationMethod(method)),
    [hasEmailVerification, hasPasswordVerification, hasPhoneVerification]
  );

  useEffect(() => {
    // Auto-select the lone available method once on mount.
    if (!verifyingMethod && availableMethods.length === 1 && !hasAutoSelectedMethod.current) {
      setVerifyingMethod(availableMethods[0]);
      // eslint-disable-next-line @silverhand/fp/no-mutation
      hasAutoSelectedMethod.current = true;
      return;
    }

    // Clear selection if it no longer exists (e.g., user info changed).
    if (verifyingMethod && !availableMethods.includes(verifyingMethod)) {
      setVerifyingMethod(undefined);
      // eslint-disable-next-line @silverhand/fp/no-mutation
      hasAutoSelectedMethod.current = false;
    }
  }, [availableMethods, verifyingMethod]);

  if (verifyingMethod === VerificationMethod.Password) {
    return (
      <PasswordVerification
        onBack={() => {
          setVerifyingMethod(undefined);
        }}
      />
    );
  }