第一个MERN博客开发时遇到的Bug

135 阅读3分钟

前端

刷新浏览器导致用户信息消失

I can't go to the component by manually inputting the address in the navigation bar of my web. Once I input any address in the bar, the user I've logged in with before will log out automatically and lose all info in my react store that I stored when I successfully logged in.

image.png

image.png

I forget to extract the login user info from the local storage of my browser. So, after refreshing my page, the login user data stored in the store will disappear.

I should try to get user info from local storage everytime the page is loaded and place them into redux info slot repectively

//get user from local storage and place into storage
const userLoginFromStorage = localStorage.getItem("userInfo") ? JSON.parse(localStorage.getItem("userInfo")) : null;
const usersSlices = createSlice({
  name: "users",
  initialState: {
    userAuth: userLoginFromStorage,
  },
 })
//userSlices.js
//Login
export const loginUserAction = createAsyncThunk(
  "user/login",
  async (userData, { rejectWithValue, getState, dispatch }) => {
    const config = {
      headers: {
        "Content-Type": "application/json",
      },
    };
    try {
      //make http call
      const { data } = await axios.post(
        `${baseUrl}/api/users/login`,
        userData,
        config,
      );
      //save user into local storage
      localStorage.setItem("userInfo", JSON.stringify(data));
      return data;
    } catch (error) {
      if (!error?.response) {
        throw error;
      }
      return rejectWithValue(error?.response?.data);
    }
  },
);

//Get user from local storage and place into storage
const userLoginFromStorage = localStorage.getItem("userInfo")
  ? JSON.parse(localStorage.getItem("userInfo"))
  : null;


//Logout action
export const logoutAction = createAsyncThunk(
  "/user/logout",
  async (payload, { rejectWithValue, getState, dispatch }) => {
    try {
      localStorage.removeItem("userInfo");
    } catch (error) {
      if (!error?.response) {
        throw error;
      }
      return rejectWithValue(error?.response?.data);
    }
  },
);

//Slices
const usersSlices = createSlice({
  name: "users",
  initialState: {
    userAuth: userLoginFromStorage,
  },
  extraReducers: (builder) => {
    //register
//login
    builder.addCase(loginUserAction.pending, (state, action) => {
      state.loading = true;
      state.appErr = undefined;
      state.serverErr = undefined;
    });
    builder.addCase(loginUserAction.fulfilled, (state, action) => {
      state.userAuth = action?.payload;
      state.loading = false;
      state.appErr = undefined;
      state.serverErr = undefined;
    });
    builder.addCase(loginUserAction.rejected, (state, action) => {
      state.appErr = action?.payload?.message;
      state.serverErr = action?.error?.message;
      state.loading = false;
    });
   },
});

export default usersSlices.reducer;

Login.js Component

//Form schema
const formSchema = Yup.object({
  email: Yup.string().required("Email is required"),
  password: Yup.string().required("Password is required"),
});

const Login = () => {
  const dispatch = useDispatch();
  //formik
  const formik = useFormik({
    initialValues: {
      email: "",
      password: "",
    },
    onSubmit: (values) => {
      //dispath the action
      dispatch(loginUserAction(values));
    },
    validationSchema: formSchema,
  });

  //redirect
  const store = useSelector((state) => state?.users);
  const { userAuth, loading, serverErr, appErr } = store;
  if (userAuth) return <Redirect to={`/profile/${userAuth?._id}`} />;
  return (
    <>
    <xxx>
    <>

usersSlices里的initialState会持续寻找是否有userLoginFromStroage,每次页面刷新都会去寻找. 所有用到userslices的组件都可以用到这个被保存了userLoginFromStorage的变量userAuth

//get user from local storage and place into storage
const userLoginFromStorage = localStorage.getItem("userInfo") ? JSON.parse(localStorage.getItem("userInfo")) : null;
const usersSlices = createSlice({
  name: "users",
  initialState: {
    userAuth: userLoginFromStorage,
  },
 })
//get user from local storage and place into storage
const userLoginFromStorage = localStorage.getItem("userInfo") ? JSON.parse(localStorage.getItem("userInfo")) : null;

const usersSlices = createSlice({
  name: "users",
  initialState: {
    userAuth: userLoginFromStorage,
  },
 })

PrivateNavbar.js Component / AdminNavbar.js Component

 <button onClick={() => dispatch(logoutAction())}  type="button"  className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-500 hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500">
        <LogoutIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true"/>
        <span>Logout</span>
</button>

CategoryDropDown组件

场景: 组件加载后从数据库获取组件数据 - categoryList并通过map遍历它们并把它们显示到其他组件中。

Bug:页面初次加载时由于网络延迟categoryList可能还没有获取到数据,对其进行遍历可能会导致map方法找不到索引对象(对象为空)。

解决办法: 在使用map遍历对象前先判断对象是否为空, 确保遍历对象不为空再进行遍历。

在这里使用setTimeout不是一个好选择因为不知道什么时候能获取到数据。 image.png

image.png

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import Select from "react-select";
import { fetchCategoriesAction } from "../../redux/slices/category/categorySlice";


const CategoryDropDown = (props) => {
  //dispatch action
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchCategoriesAction());
  }, [dispatch]);
  //select categories
  const category = useSelector((state) => state?.category);
  const { categoryList, loading, appErr, serverErr } = category;
  // console.log(categoryList);
  let allCategories = null;
  // in order to prevent categoryList is empty when executing map function for categoryList, we need to make sure that
  // the categoryList property is loaded before use map to iterate it.
  if (categoryList) {
    allCategories = categoryList?.map((category) => {
      return {
        label: category?.title,
        value: category?._id,
      };
    });
  }
  
  return (
        <Select
          onChange={handleChange}
          onBlur={handleBlur}
          id="category"
          options={allCategories}
          value={props?.value?.label}
        />
      )}

后端

在项目文件夹外面文件夹使用npm i 在项目文件夹中使用yarn add添加包没有作用

image.png 原因: node_modules文件夹没有在项目根目录下

使用sendgrid发送forgetPasswordToken两次设置发送请求头

调用forgetPasswordToken方法时,终端报错: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

image.png

 try {
      const token = await user.createPasswordResetToken();
     // console.log(token);
      await user.save();
      // Build your message
     const resetURL = `If you were requested to reset your password, reset now within 10 minutes, otherwise ignore this message <a href="http://localhost:3000/reset-password/${token}">Click to verify your account</a>`;
      const msg = {
          xxx: xxx
      };
      const emailMsg = await sgMail.send(msg);
      res.json(emailMsg);
    } catch (err) {
      res.json(err);
    }

解决办法: Put return in front of this as return res.send("forget password");

image.png

Can't push emailMsg data to mongoDb

After I use await emailMsg.create() and fill in all info required by emailMsgSchema, I still cannot push this email message to db

image.png

问题出在ssendEmailMsgCtrl.js末尾的函数暴露没有使用正确的格式-应该用花括号把想要暴露的函数包起来做模块化暴露而不是直接module.exports = sendEmailMsgCtrl;模块暴露加函数名 -- 这样暴露的内容为空,module.exports向外暴露的是一个对象{},这个对象在这里没有用解构赋值语句{}接受对应函数暴露的内容就是空对象{}

image.png

解决办法:在模块暴露的地方对想暴露的函数加上{}即可

module.exports = { sendEmailMsgCtrl };

部署

.gitignore 文件没有被执行

向gitignore中添加不需要添加到git缓存的目标指令后,git add .等待很久的时间, 似乎.gitignore没有被执行。

image.png

解决办法: 因为之前没有规则的.gitignore文件或者说项目文件夹索引已经checkin了,要先git rm --cached .gitignore,删除再新建gitignore往里加rule。

在确保了.gitignore 文件放在了根目录下,内容没有打错后。用git rm --cached gitignore命令删除存在本地git cache中的文件,之后在本地根目录下删除.gitignore文件。在根目录下新建.gitignore文件并添加规则,之后使用git status查看发现写在gitignore中希望被屏蔽的文件消失了。