使用React Hooks处理身份验证

时间:2020-09-04 00:44:48

标签: reactjs firebase firebase-authentication react-hooks

我已经追了好几个小时,试图弄清楚如何使用firebase处理我的组件上的auth并做出反应。

我创建了一个自定义的useAuth钩子,该钩子是打算的,用于处理所有身份验证行为。我的想法是在组件树的根上放置一个useEffect,如果firebase.auth.onAuthStateChanged()曾经更改过(即,用户现在已注销/登录),它将触发。一百万个失败的更改,我真的不知道自己在做什么。

这是我拥有的代码...

RootPage组件

const RootPage = ({ Component, pageProps }): JSX.Element => {
  const { logoutUser, authStatus } = useAuth();
  const router = useRouter();

  useEffect(() => {
    authStatus();
  }, [authStatus]);

  ...
}

我的想法没问题,让我们在挂载时触发authStatus,但这最终使我躺在依赖关系上。因此,为了不撒谎,我在部门中添加了authStatus。注销然后登录,结果如下:

enter image description here

useAuth钩子

const useAuth = () => {
  const { fetchUser, resetUser, userData } = useUser();
  const { currentUser } = firebaseAuth;

  const registerUser = async (username, email, password) => {
    try {
      const credentials = await firebaseAuth.createUserWithEmailAndPassword(
        email,
        password
      );
      const { uid } = credentials.user;
      await firebaseFirestore
        .collection('users')
        .doc(credentials.user.uid)
        .set({
          username,
          points: 0,
          words: 0,
          followers: 0,
          following: 0,
          created: firebase.firestore.FieldValue.serverTimestamp(),
        });
      fetchUser(uid);
      console.log('user registered', credentials);
    } catch (error) {
      console.error(error);
    }
  };

  const loginUser = async (email, password) => {
    try {
      // login to firebase
      await firebaseAuth.signInWithEmailAndPassword(email, password);
      // take the current users id
      const { uid } = firebaseAuth.currentUser;
      // update the user in redux
      fetchUser(uid);
    } catch (error) {
      console.error(error);
    }
  };

  const logoutUser = async () => {
    try {
      // logout from firebase
      await firebaseAuth.signOut();
      // reset user state in redux
      resetUser();
      return;
    } catch (error) {
      console.error(error);
    }
  };

  const authStatus = () => {
    firebaseAuth.onAuthStateChanged((user) => {
      if (user) {
        console.log('User logged in.');
        // On page refresh, if user persists (but redux state is lost), update user in redux
        if (userData === initialUserState) {
          console.log('triggered');
          // update user in redux store with data from user collection
          fetchUser(user.uid);
        }
        return;
      }
      console.log('User logged out.');
    });
  };

  return { currentUser, registerUser, loginUser, logoutUser, authStatus };
};

export default useAuth;

3 个答案:

答案 0 :(得分:0)

我知道为什么会发生这个问题,但不知道解决方案...但是我不确定...看看有什么反应是父母是否重新渲染它也会导致孩子重新渲染..ok?它的意思是,如果有任何原因导致您的应用程序正在重新渲染并且useAuth一直处于触发状态,那么这会导致大量控制台日志。但是我不确定它是否可以正常工作..请给我您的repo,我将在本地尝试电脑

const RootPage = ({ Component, pageProps }): JSX.Element => {
  const { logoutUser, authStatus,currentUser } = useAuth();
  const router = useRouter();

  useEffect(() => {
    authStatus();
  }, [currentUser]);
  
  //only fire when currentUser change

  ...
}

答案 1 :(得分:0)

像这样更新你的 useEffect 钩子:

useEffect(() => {
  const unsub = firebaseAuth.onAuthStateChanged((user) => {
      if (user) {
        console.log('User logged in.');
        // On page refresh, if user persists (but redux state is lost), update user in redux
        if (userData === initialUserState) {
          console.log('triggered');
          // update user in redux store with data from user collection
          fetchUser(user.uid);
        }
      } else {
        console.log('User logged out.');
      }
    });
  return ()=> unsub;
},[])

答案 2 :(得分:0)

我相对确定 React 钩子仅用于可重用的逻辑部分,因此如果您的钩子的目的是在您使用的每个组件中联系 firebase,以及每次重新渲染和刷新状态组件更新了就可以了,但是不能使用钩子来存储全局身份验证状态,而身份验证应该是这样存储的。

您正在寻找反应上下文。

import React, {createContext, useContext, useState, useEffect, ReactNode} from 'react'

const getJwt = () => localStorage.getItem('jwt') || ''

const setJwt = (jwt: string) => localStorage.setItem('jwt', jwt)

const getUser = () => JSON.parse(localStorage.getItem('user') || 'null')

const setUser = (user: object) => localStorage.setItem('user', JSON.stringify(user))

const logout = () => localStorage.clear()

const AuthContext = createContext({
  jwt: '',
  setJwt: setJwt,
  user: {},
  setUser: setUser,
  loading: false,
  setLoading: (loading: boolean) => {},
  authenticate: (jwt: string, user: object) => {},
  logout: () => {},
})

export const useAuth = () => useContext(AuthContext)

const Auth = ({children}: {children: ReactNode}) => {
  const auth = useAuth()

  const [jwt, updateJwt] = useState(auth.jwt)
  const [user, updateUser] = useState(auth.user)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    updateJwt(getJwt())
    updateUser(getUser())
  }, [])

  const value = {
    jwt: jwt,
    setJwt: (jwt: string) => {
      setJwt(jwt)
      updateJwt(jwt)
    },
    user: user,
    setUser: (user: object) => {
      setUser(user)
      updateUser(user)
    },
    loading: loading,
    setLoading: setLoading,
    authenticate: (jwt: string, user: object) => {
      setJwt(jwt)
      updateJwt(jwt)
      setUser(user)
      updateUser(user)
    },
    logout: () => {
      localStorage.removeItem('jwt')
      localStorage.removeItem('user')
      updateJwt('')
      updateUser({})
      setLoading(false)
    },
  }

  return <AuthContext.Provider value={value}>
    {children}
  </AuthContext.Provider>
}

export default Auth

...

// app.tsx

import Auth from './auth'

...

<Auth>
  <Router/>
</Auth>

// or something like that

...

import {useAuth} from './auth'

// in any component to pull auth from global context state

您可以根据需要进行更改。