You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Formik注册功能异常求助:Firestore未写入、页面未跳转

问题排查与解决

问题概述

使用React、TypeScript、Formik和Firebase开发学习项目,Firebase认证可正常创建用户,但存在以下问题:

  • 页面无法跳转至/main路由
  • 表单提交后未自动清空
  • 用户数据未写入Firestore

代码片段(Registration.tsx)

import React from "react";

import { Formik, Form, Field, ErrorMessage, FormikHelpers } from "formik";
import * as Yup from "yup";

// Firebase
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { collection, getFirestore } from "firebase/firestore";

import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";

// Navigate
import { useNavigate } from "react-router-dom";

// Initialize Firebase
const app = firebase.initializeApp(firebaseConfig);
const auth = getAuth(app);
const firestore = getFirestore(app);

const Registration: React.FC = () => {
  const navigate = useNavigate();

  const handleRedirect = () => {
    navigate("/main");
  };

  const initialValues = {
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
  };

  const validationSchema = Yup.object({
    name: Yup.string().required("Name is required"),
    email: Yup.string().email("Invalid email").required("Email is required"),
    password: Yup.string()
      .required("Password is required")
      .min(8, "Password must be at least 8 characters")
      .matches(
        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
        "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
      ),
    confirmPassword: Yup.string()
      .test("password-match", "Passwords must match", function (value) {
        return value === this.resolve(Yup.ref("password"));
      })
      .nullable()
      .required("Confirm Password is required"),
  });

  const registrationSubmit = async (
    values: typeof initialValues,
    { resetForm }: FormikHelpers<typeof initialValues>
  ) => {
    try {
      const { name, email, password } = values;

      if (name && email && password) {
        const userCredential = await firebase
          .auth()
          .createUserWithEmailAndPassword(email, password);

        await userCredential.user?.updateProfile({ displayName: name });

        try {
          console.log(userCredential.user?.uid);

          const db = firebase.firestore();

          await db.collection("users").doc(userCredential.user?.uid).set({
            name,
            email,
            password,
          });

          console.log("Registration was successful");
          resetForm();
          navigate("/main");
        } catch (error) {
          console.error("Error writing to Firestore:", error);
        }
      } else {
        console.log("Please provide all required fields");
      }
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div className="page bg-teal-50">
      <div className="container flex flex-col justify-center align-center">
        <h1 className="text-5xl font-bold text-center text-gray-800">
          Registration
        </h1>
        <Formik
          initialValues={initialValues}
          validationSchema={validationSchema}
          onSubmit={registrationSubmit}
        >
          {({ errors, touched }) => (
            <Form className="flex flex-col m-auto" autoComplete="off">
              <div className="flex mt-16 items-center justify-center flex-col ">
                <Field
                  type="text"
                  id="name"
                  name="name"
                  placeholder="Username"
                  className={`w-64 h-12 text-gray-900 pl-4 outline-none border-solid border-2 rounded focus:border-teal-600 ${
                    errors.name && touched.name
                      ? "border-red-500"
                      : "border-teal-400"
                  }`}
                />{" "}
                <ErrorMessage
                  name="name"
                  component="p"
                  className="text-red-600 font-bold text-lg flex align-center mt-4"
                />
              </div>
              <div className="flex mt-4 items-center justify-center flex-col ">
                <Field
                  type="email"
                  id="email"
                  name="email"
                  placeholder="Email"
                  className={`w-64 h-12 text-gray-900 pl-4 outline-none border-solid border-2 rounded focus:border-teal-600 ${
                    errors.name && touched.name
                      ? "border-red-500"
                      : "border-teal-400"
                  }`}
                />
                <ErrorMessage
                  name="email"
                  component="p"
                  className="text-red-600 font-bold text-lg flex align-center mt-4"
                />
              </div>
              <div className="flex mt-4 items-center justify-center flex-col ">
                <Field
                  type="password"
                  id="password"
                  name="password"
                  placeholder="Password"
                  className={`w-64 h-12 text-gray-900 pl-4 outline-none border-solid border-2 rounded focus:border-teal-600 ${
                    errors.name && touched.name
                      ? "border-red-500"
                      : "border-teal-400"
                  }`}
                />
                <ErrorMessage
                  name="password"
                  component="p"
                  className="text-red-600 font-bold text-lg flex align-center mt-4"
                />
              </div>
              <div className="flex mt-4 items-center justify-center flex-col ">
                <Field
                  type="password"
                  id="confirmPassword"
                  name="confirmPassword"
                  placeholder="Confirm Password"
                  className={`w-64 h-12 text-gray-900 pl-4 outline-none border-solid border-2 rounded focus:border-teal-600 ${
                    errors.name && touched.name
                      ? "border-red-500"
                      : "border-teal-400"
                  }`}
                />
                <ErrorMessage
                  name="confirmPassword"
                  component="p"
                  className="text-red-600 font-bold text-lg flex align-center mt-4"
                />
              </div>
              <button
                type="submit"
                className="bg-blue-500 w-32 h-12 cursor-pointer mt-8 m-auto text-white text-xl rounded hover:bg-blue-700 transition-colors duration-300"
              >
                Register
              </button>
            </Form>
          )}
        </Formik>
      </div>
    </div>
  );
};

export default Registration;

问题排查与修复方案

1. Firestore写入失败的核心原因

(1)安全规则限制

Firebase默认的Firestore安全规则禁止未认证用户的读写操作,即使已创建用户,若规则未配置,写入会被拒绝。需修改Firestore安全规则为:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow create, read, update, delete: if request.auth != null && request.auth.uid == userId;
    }
  }
}

此规则仅允许认证用户写入自己的文档,确保数据安全。

(2)用户UID为空判断缺失

代码中userCredential.user?.uid存在为空的可能,导致写入文档的ID为undefined触发错误。需添加非空判断:

if (userCredential.user) {
  await db.collection("users").doc(userCredential.user.uid).set({
    name,
    email,
    // 重要:不要存储明文密码!Firebase Auth会自动管理密码,无需额外存储
    // password,
  });
}

(3)Firebase实例混用

代码同时导入了Firebase模块化API和兼容版API,可能导致实例冲突。建议统一使用兼容版:

// 移除模块化API导入
// import { initializeApp } from "firebase/app";
// import { getAuth } from "firebase/auth";
// import { collection, getFirestore } from "firebase/firestore";

// 保留兼容版导入
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";

// 初始化
const app = firebase.initializeApp(firebaseConfig);
const auth = firebase.auth(app);
const firestore = firebase.firestore(app);

2. 页面不跳转、表单不清空的原因

这两个问题是Firestore写入失败的连锁反应:代码中resetForm()和navigate("/main")仅在Firestore写入成功后执行,一旦写入失败进入catch块,这两个操作就不会触发。修复Firestore写入问题后,这两个功能会自动恢复。

3. 额外UI问题(非核心但需修复)

所有Field的错误样式判断都绑定了errors.name && touched.name,导致email、password等字段的错误状态与name字段关联,需修改为对应字段的判断:
比如email字段的className应改为:

className={`w-64 h-12 text-gray-900 pl-4 outline-none border-solid border-2 rounded focus:border-teal-600 ${
  errors.email && touched.email
    ? "border-red-500"
    : "border-teal-400"
}`}

password和confirmPassword字段同理,替换为各自的errors.password、errors.confirmPassword。

内容的提问来源于stack exchange,提问作者Ruslan Makhmatov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:27:32