【问题标题】:Firebase listener with React Hooks带有 React Hooks 的 Firebase 监听器
【发布时间】:2020-05-15 14:04:33
【问题描述】:

我正在尝试弄清楚如何使用 Firebase 侦听器,以便通过反应挂钩更新来刷新云 Firestore 数据。

最初,我使用带有 componentDidMount 函数的类组件来获取 Firestore 数据。

this.props.firebase.db
    .collection('users')
    // .doc(this.props.firebase.db.collection('users').doc(this.props.firebase.authUser.uid))
.doc(this.props.firebase.db.collection('users').doc(this.props.authUser.uid))
.get()
.then(doc => {
    this.setState({ name: doc.data().name });
    // loading: false,
  });  
}

当页面更新时会中断,所以我试图弄清楚如何移动侦听器以响应钩子。

我已经安装了 react-firebase-hooks 工具 - 虽然我不知道如何阅读说明才能使其工作。

我有一个函数组件如下:

import React, { useState, useEffect } from 'react';
import { useDocument } from 'react-firebase-hooks/firestore';

import {
    BrowserRouter as Router,
    Route,
    Link,
    Switch,
    useRouteMatch,
 } from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification, withAuthentication } from '../Session/Index';

function Dashboard2(authUser) {
    const FirestoreDocument = () => {

        const [value, loading, error] = useDocument(
          Firebase.db.doc(authUser.uid),
          //firebase.db.doc(authUser.uid),
          //firebase.firestore.doc(authUser.uid),
          {
            snapshotListenOptions: { includeMetadataChanges: true },
          }
        );
    return (

        <div>    



                <p>
                    {error && <strong>Error: {JSON.stringify(error)}</strong>}
                    {loading && <span>Document: Loading...</span>}
                    {value && <span>Document: {JSON.stringify(value.data())}</span>}
                </p>




        </div>

    );
  }
}

export default withAuthentication(Dashboard2);

此组件在路由级别被包装在 authUser 包装器中,如下所示:

<Route path={ROUTES.DASHBOARD2} render={props => (
          <AuthUserContext.Consumer>
             { authUser => ( 
                <Dashboard2 authUser={authUser} {...props} />  
             )}
          </AuthUserContext.Consumer>
        )} />

我有一个 firebase.js 文件,它按如下方式插入到 firestore:

class Firebase {
  constructor() {
    app.initializeApp(config).firestore();
    /* helpers */
    this.fieldValue = app.firestore.FieldValue;


    /* Firebase APIs */
    this.auth = app.auth();
    this.db = app.firestore();


  }

它还定义了一个监听器来知道 authUser 何时发生变化:

onAuthUserListener(next, fallback) {
    // onUserDataListener(next, fallback) {
      return this.auth.onAuthStateChanged(authUser => {
        if (authUser) {
          this.user(authUser.uid)
            .get()
            .then(snapshot => {
            let snapshotData = snapshot.data();

            let userData = {
              ...snapshotData, // snapshotData first so it doesn't override information from authUser object
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerifed,
              providerData: authUser.providerData
            };

            setTimeout(() => next(userData), 0); // escapes this Promise's error handler
          })

          .catch(err => {
            // TODO: Handle error?
            console.error('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
            setTimeout(fallback, 0); // escapes this Promise's error handler
          });

        };
        if (!authUser) {
          // user not logged in, call fallback handler
          fallback();
          return;
        }
    });
  };

然后,在我的 firebase 上下文设置中,我有:

import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };

上下文在 withFirebase 包装器中设置如下:

import React from 'react';
const FirebaseContext = React.createContext(null);

export const withFirebase = Component => props => (
  <FirebaseContext.Consumer>
    {firebase => <Component {...props} firebase={firebase} />}
  </FirebaseContext.Consumer>
);
export default FirebaseContext;

然后,在我的 withAuthentication HOC 中,我有一个上下文提供程序:

import React from 'react';
import { AuthUserContext } from '../Session/Index';
import { withFirebase } from '../Firebase/Index';

const withAuthentication = Component => {
  class WithAuthentication extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        authUser: null,
      };  
    }

    componentDidMount() {
      this.listener = this.props.firebase.auth.onAuthStateChanged(
        authUser => {
           authUser
            ? this.setState({ authUser })
            : this.setState({ authUser: null });
        },
      );
    }

    componentWillUnmount() {
      this.listener();
    };  

    render() {
      return (
        <AuthUserContext.Provider value={this.state.authUser}>
          <Component {...this.props} />
        </AuthUserContext.Provider>
      );
    }
  }
  return withFirebase(WithAuthentication);

};
export default withAuthentication;

目前 - 当我尝试此操作时,我在 Dashboard2 组件中收到错误消息:

Firebase' 未定义

我尝试了小写 firebase 并得到了同样的错误。

我还尝试了 firebase.firestore 和 Firebase.firestore。我得到同样的错误。

我想知道我的 HOC 是否不能与功能组件一起使用?

我见过这个demo app 和这个blog post

按照博客中的建议,我创建了一个新的 firebase/contextReader.jsx:

 import React, { useEffect, useContext } from 'react';
import Firebase from '../../firebase';



export const userContext = React.createContext({
    user: null,
  })

export const useSession = () => {
    const { user } = useContext(userContext)
    return user
  }

  export const useAuth = () => {
    const [state, setState] = React.useState(() => 
        { const user = firebase.auth().currentUser 
            return { initializing: !user, user, } 
        }
    );
    function onChange(user) {
      setState({ initializing: false, user })
    }

    React.useEffect(() => {
      // listen for auth state changes
      const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
      // unsubscribe to the listener when unmounting
      return () => unsubscribe()
    }, [])

    return state
  }  

然后我尝试将我的 App.jsx 包装在该阅读器中:

function App() {
  const { initializing, user } = useAuth()
  if (initializing) {
    return <div>Loading</div>
  }

    // )
// }
// const App = () => (
  return (
    <userContext.Provider value={{ user }}> 


    <Router>
        <Navigation />
        <Route path={ROUTES.LANDING} exact component={StandardLanding} />

当我尝试这个时,我收到一条错误消息:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2__.default.auth 不是 一个函数

我已经看到 this post 处理该错误并尝试卸载并重新安装纱线。没有区别。

当我查看demo app 时,它建议应该使用“接口”方法创建上下文。我看不出这是从哪里来的 - 我在文档中找不到解释它的参考资料。

除了尝试我所做的插入它之外,我无法理解这些说明。

我看到this post 尝试在不使用 react-firebase-hooks 的情况下监听 firestore。答案指向试图弄清楚如何使用这个工具。

我已阅读 this excellent explanation,其中介绍了如何从 HOC 转移到钩子。我不知道如何集成 firebase 监听器。

我见过this post,它为如何考虑这样做提供了一个有用的示例。不确定我是否应该尝试在 authListener componentDidMount 中执行此操作 - 还是在尝试使用它的 Dashboard 组件中执行此操作。

下一次尝试 我找到了this post,它试图解决同样的问题。

当我尝试实施 Shubham Khatri 提供的解决方案时,我将 firebase 配置设置如下:

上下文提供者: 从“反应”导入反应,{useContext}; 从 '../../firebase' 导入 Firebase;

const FirebaseContext = React.createContext(); 

export const FirebaseProvider = (props) => ( 
   <FirebaseContext.Provider value={new Firebase()}> 
      {props.children} 
   </FirebaseContext.Provider> 
); 

上下文钩子有:

import React, { useEffect, useContext, useState } from 'react';

const useFirebaseAuthentication = (firebase) => {
    const [authUser, setAuthUser] = useState(null);

    useEffect(() =>{
       const unlisten = 
firebase.auth.onAuthStateChanged(
          authUser => {
            authUser
              ? setAuthUser(authUser)
              : setAuthUser(null);
          },
       );
       return () => {
           unlisten();
       }
    });

    return authUser
}

export default useFirebaseAuthentication;

然后在 index.js 中,我将提供程序中的应用程序包装为:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App/Index';
import {FirebaseProvider} from './components/Firebase/ContextHookProvider';

import * as serviceWorker from './serviceWorker';


ReactDOM.render(

    <FirebaseProvider> 
    <App /> 
    </FirebaseProvider>,
    document.getElementById('root')
);

    serviceWorker.unregister();

然后,当我尝试在组件中使用监听器时:

import React, {useContext} from 'react';
import { FirebaseContext } from '../Firebase/ContextHookProvider';
import useFirebaseAuthentication from '../Firebase/ContextHook';


const Dashboard2 = (props) => {
    const firebase = useContext(FirebaseContext);
    const authUser = 
useFirebaseAuthentication(firebase);

    return (
        <div>authUser.email</div>
    )
 }

 export default Dashboard2;

我尝试将其用作没有组件或身份验证包装器的路由:

<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />

当我尝试这个时,我收到一条错误消息:

尝试导入错误:“FirebaseContext”未从中导出 '../Firebase/ContextHookProvider'.

该错误消息是有道理的,因为 ContextHookProvider 不导出 FirebaseContext - 它导出 FirebaseProvider - 但如果我不尝试在 Dashboard2 中导入它 - 那么我无法在尝试使用它的函数中访问它。

这种尝试的一个副作用是我的注册方法不再有效。它现在会生成一条错误消息:

TypeError:无法读取属性“doCreateUserWithEmailAndPassword” 空

我稍后会解决这个问题 - 但必须有一种方法来弄清楚如何使用 react with firebase,而不需要通过数百万条无法获得基本身份验证设置的途径进行数月的循环。是否有适用于 react hooks 的 firebase(firestore)入门工具包?

下一次尝试 我尝试遵循udemy course 中的方法 - 但它仅适用于生成表单输入 - 没有侦听器可以放置路由以根据经过身份验证的用户进行调整。

我尝试遵循此youtube tutorial 中的方法 - 可以使用此repo。它展示了如何使用钩子,但没有展示如何使用上下文。

下一次尝试 我发现 this repo 似乎有一个深思熟虑的方法来使用带有 firestore 的钩子。但是,我无法理解代码。

我克隆了这个 - 并尝试添加所有公共文件,然后当我运行它时 - 我实际上无法让代码运行。我不确定如何让它运行的说明中缺少什么,以便查看代码中是否有可以帮助解决此问题的课程。

下一次尝试

我买了 divjoy 模板,宣传它是为 firebase 设置的(它不是为 firestore 设置的,以防其他人考虑这是一个选项)。

该模板提出了一个身份验证包装器,用于初始化应用程序的配置 - 但仅用于身份验证方法 - 因此需要对其进行重组以允许另一个上下文提供程序用于 firestore。当你混淆了这个过程并使用this post中显示的过程时,剩下的就是以下回调中的错误:

useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

它不知道什么是 firebase。这是因为它是在 firebase 上下文提供程序中定义的,该提供程序被导入并定义为(在 useProvideAuth() 函数中):

  const firebase = useContext(FirebaseContext)

没有机会回调,错误说:

React Hook useEffect 缺少一个依赖项:'firebase'。任何一个 包含它或删除依赖数组

或者,如果我尝试将该 const 添加到回调中,我会收到一条错误消息:

无法在回调中调用 React Hook "useContext"。反应 Hooks 必须在 React 函数组件或自定义 React 中调用 挂钩功能

下一次尝试

我已将我的 firebase 配置文件缩减为仅配置变量(我将在上下文提供程序中为我要使用的每个上下文编写帮助程序)。

import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

const devConfig = {
    apiKey: process.env.REACT_APP_DEV_API_KEY,
    authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
    projectId: process.env.REACT_APP_DEV_PROJECT_ID,
    storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_DEV_APP_ID

  };


  const prodConfig = {
    apiKey: process.env.REACT_APP_PROD_API_KEY,
    authDomain: process.env.REACT_APP_PROD_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_PROD_DATABASE_URL,
    projectId: process.env.REACT_APP_PROD_PROJECT_ID,
    storageBucket: process.env.REACT_APP_PROD_STORAGE_BUCKET,
    messagingSenderId: 
process.env.REACT_APP_PROD_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_PROD_APP_ID
  };

  const config =
    process.env.NODE_ENV === 'production' ? prodConfig : devConfig;


class Firebase {
  constructor() {
    firebase.initializeApp(config);
    this.firebase = firebase;
    this.firestore = firebase.firestore();
    this.auth = firebase.auth();
  }
};

export default Firebase;  

然后我有一个身份验证上下文提供程序,如下所示:

import React, { useState, useEffect, useContext, createContext } from "react";
import Firebase from "../firebase";

const authContext = createContext();

// Provider component that wraps app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
  const auth = useProvideAuth();

  return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}

// Hook for child components to get the auth object ...
// ... and update when it changes.
export const useAuth = () => {

  return useContext(authContext);
};

// Provider hook that creates auth object and handles state
function useProvideAuth() {
  const [user, setUser] = useState(null);


  const signup = (email, password) => {
    return Firebase
      .auth()
      .createUserWithEmailAndPassword(email, password)
      .then(response => {
        setUser(response.user);
        return response.user;
      });
  };

  const signin = (email, password) => {
    return Firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(response => {
        setUser(response.user);
        return response.user;
      });
  };



  const signout = () => {
    return Firebase
      .auth()
      .signOut()
      .then(() => {
        setUser(false);
      });
  };

  const sendPasswordResetEmail = email => {
    return Firebase
      .auth()
      .sendPasswordResetEmail(email)
      .then(() => {
        return true;
      });
  };

  const confirmPasswordReset = (password, code) => {
    // Get code from query string object
    const resetCode = code || getFromQueryString("oobCode");

    return Firebase
      .auth()
      .confirmPasswordReset(resetCode, password)
      .then(() => {
        return true;
      });
  };

  // Subscribe to user on mount
  useEffect(() => {

    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

    // Subscription unsubscribe function
    return () => unsubscribe();
  }, []);

  return {
    user,
    signup,
    signin,
    signout,
    sendPasswordResetEmail,
    confirmPasswordReset
  };
}

const getFromQueryString = key => {
  return queryString.parse(window.location.search)[key];
};

我还制作了一个 Firebase 上下文提供程序,如下所示:

import React, { createContext } from 'react';
import Firebase from "../../firebase";

const FirebaseContext = createContext(null)
export { FirebaseContext }


export default ({ children }) => {

    return (
      <FirebaseContext.Provider value={ Firebase }>
        { children }
      </FirebaseContext.Provider>
    )
  }

然后,在 index.js 中,我将应用程序包装在 firebase 提供程序中

ReactDom.render(
    <FirebaseProvider>
        <App />
    </FirebaseProvider>, 
document.getElementById("root"));

serviceWorker.unregister();

在我的路由列表中,我已将相关路由包装在身份验证提供程序中:

import React from "react";
import IndexPage from "./index";
import { Switch, Route, Router } from "./../util/router.js";

import { ProvideAuth } from "./../util/auth.js";

function App(props) {
  return (
    <ProvideAuth>
      <Router>
        <Switch>
          <Route exact path="/" component={IndexPage} />

          <Route
            component={({ location }) => {
              return (
                <div
                  style={{
                    padding: "50px",
                    width: "100%",
                    textAlign: "center"
                  }}
                >
                  The page <code>{location.pathname}</code> could not be found.
                </div>
              );
            }}
          />
        </Switch>
      </Router>
    </ProvideAuth>
  );
}

export default App;

在这个特定的尝试中,我回到了之前用这个错误标记的问题:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2__.default.auth 不是 一个函数

它指向身份验证提供程序的这一行作为创建问题:

useEffect(() => {

    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

我曾尝试在 Firebase 中使用大写的 F,但它会产生相同的错误。

当我尝试 Tristan 的建议时,我删除了所有这些内容,并尝试将我的 unsubscribe 方法定义为 unlisten 方法(我不知道他为什么不使用 firebase 语言 - 但如果他的方法有效,我' d 更加努力地找出原因)。当我尝试使用他的解决方案时,错误消息显示:

类型错误: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8___default(...) 不是函数

this post 的答案建议在 auth 之后删除 ()。当我尝试这样做时,我收到一条错误消息:

TypeError: 无法读取未定义的属性“onAuthStateChanged”

但是this post 建议在 auth 文件中导入 firebase 的方式存在问题。

我将其导入为:import Firebase from "../firebase";

Firebase 是类的名称。

Tristan 推荐的视频是很有帮助的背景,但我目前正在观看第 9 集,但仍然没有找到应该帮助解决这个问题的部分。有人知道在哪里可以找到吗?

下一次尝试 接下来 - 并尝试仅解决上下文问题 - 我已导入 createContext 和 useContext 并尝试使用它们,如本文档中所示。

我无法通过错误提示:

错误:无效的挂钩调用。钩子只能在体内调用 的一个功能组件。这可能发生在以下情况之一 原因:...

我已通过this link 中的建议尝试解决此问题,但无法解决。我没有遇到此故障排除指南中显示的任何问题。

目前 - 上下文语句如下所示:

import React, {  useContext } from 'react';
import Firebase from "../../firebase";


  export const FirebaseContext = React.createContext();

  export const useFirebase = useContext(FirebaseContext);

  export const FirebaseProvider = props => (
    <FirebaseContext.Provider value={new Firebase()}>
      {props.children}
    </FirebaseContext.Provider>
  );  

我花了一些时间使用这个udemy course 来尝试找出这个问题的上下文和挂钩元素 - 在观看之后 - 下面 Tristan 提出的解决方案的唯一方面是 createContext 方法没有被正确调用他的职位。它需要是“React.createContext”,但它仍然无法解决问题。

我还是卡住了。

谁能看到这里出了什么问题?

【问题讨论】:

  • 未定义,因为您没有导入它。
  • 只需要将export添加到export const FirebaseContext吗?
  • 嗨,Mel 为回复缓慢而道歉,我已经疯狂地工作了两个星期,所以晚上看电脑是不可能的!我已经为您更新了我的答案,以提供一个您可以检查的干净且非常简单的解决方案。
  • 非常感谢 - 我现在就看看。
  • 嘿,梅尔,刚刚进一步更新了从 Firestore 实时更新的正确实现(可以删除 onSnapshot 部分使其不是实时的)如果这是解决方案,我可以建议可能会更新您的问题以使其更短更简洁,以便其他查看它的人也可以找到解决方案,谢谢 - 再次抱歉响应缓慢

标签: javascript reactjs firebase google-cloud-firestore react-hooks


【解决方案1】:

主要修改: 花了一些时间对此进行更多研究,这是我提出的更清洁的解决方案,有人可能不同意我认为这是解决此问题的好方法。

使用Firebase 身份验证挂钩

import { useEffect, useState, useCallback } from 'react';
import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxx",
  authDomain: "xxxx.firebaseapp.com",
  databaseURL: "https://xxxx.firebaseio.com",
  projectId: "xxxx",
  storageBucket: "xxxx.appspot.com",
  messagingSenderId: "xxxxxxxx",
  appId: "1:xxxxxxxxxx:web:xxxxxxxxx"
};

firebase.initializeApp(firebaseConfig)

const useFirebase = () => {
  const [authUser, setAuthUser] = useState(firebase.auth().currentUser);

  useEffect(() => {
    const unsubscribe = firebase.auth()
      .onAuthStateChanged((user) => setAuthUser(user))
    return () => {
      unsubscribe()
    };
  }, []);

  const login = useCallback((email, password) => firebase.auth()
    .signInWithEmailAndPassword(email, password), []);

  const logout = useCallback(() => firebase.auth().signOut(), [])

  return { login, authUser, logout }
}

export { useFirebase }

如果 authUser 为 null 则未通过身份验证,如果用户有值则通过身份验证。

firebaseConfig 可以在 firebase 控制台 => 项目设置 => 应用程序 => 配置单选按钮

useEffect(() => {
  const unsubscribe = firebase.auth()
    .onAuthStateChanged(setAuthUser)

  return () => {
    unsubscribe()
  };
}, []);

这个 useEffect 钩子是跟踪用户的 authChanges 的核心。我们向 firebase.auth() 的 onAuthStateChanged 事件添加一个监听器,用于更新 authUser 的值。此方法返回一个用于取消订阅此侦听器的回调,我们可以在刷新 useFirebase 挂钩时使用它来清理侦听器。

这是我们唯一需要进行 firebase 身份验证的钩子(可以为 firestore 等制作其他钩子。

const App = () => {
  const { login, authUser, logout } = useFirebase();

  if (authUser) {
    return <div>
      <label>User is Authenticated</label>
      <button onClick={logout}>Logout</button>
    </div>
  }

  const handleLogin = () => {
    login("name@email.com", "password0");
  }

  return <div>
    <label>User is not Authenticated</label>
    <button onClick={handleLogin}>Log In</button>
  </div>
}

这是create-react-appApp 组件的基本实现

使用Firestore 数据库挂钩

const useFirestore = () => {
  const getDocument = (documentPath, onUpdate) => {
    firebase.firestore()
      .doc(documentPath)
      .onSnapshot(onUpdate);
  }

  const saveDocument = (documentPath, document) => {
    firebase.firestore()
      .doc(documentPath)
      .set(document);
  }

  const getCollection = (collectionPath, onUpdate) => {
    firebase.firestore()
      .collection(collectionPath)
      .onSnapshot(onUpdate);
  }

  const saveCollection = (collectionPath, collection) => {
    firebase.firestore()
      .collection(collectionPath)
      .set(collection)
  }

  return { getDocument, saveDocument, getCollection, saveCollection }
}

这可以像这样在你的组件中实现:

const firestore = useFirestore();
const [document, setDocument] = useState();

const handleGet = () => {
  firestore.getDocument(
    "Test/ItsWFgksrBvsDbx1ttTf", 
    (result) => setDocument(result.data())
  );
}

const handleSave = () => {
  firestore.saveDocument(
    "Test/ItsWFgksrBvsDbx1ttTf", 
    { ...document, newField: "Hi there" }
  );

}

这消除了对 React useContext 的需求,因为我们直接从 firebase 本身获取更新。

注意几点:

  1. 保存未更改的文档不会触发新快照,因此“过度保存”不会导致重新渲染
  2. 在调用 getDocument 时,回调 onUpdate 会立即使用初始“​​快照”调用,因此您不需要额外的代码来获取文档的初始状态。

编辑删除了大部分旧答案

【讨论】:

  • 感谢您。我看不出你是如何设置的。对于提供程序,我收到一条错误消息,指出未定义 createContext。那是因为到目前为止还没有消费者。你把你的放在哪里了?
  • 嘿抱歉 createContext 是 react 的一部分,所以将它作为 { createContext } from 'react' 在顶部导入我意识到我忘了显示 Firebase 提供程序的去向,我将编辑答案跨度>
  • 我确实在提供程序中导入了它,但它呈现为未定义。我认为那是因为它没有消费者
  • 消费者是 useContext() 钩子,但再次查看您的问题,您似乎没有从文件中导出 FirebaseContext - 这就是它找不到上下文的原因:)
  • 嗨@Mel 谢谢你,非常好,我希望它最终会有所帮助。 Hooks 和 Firebase 都参与其中,我花了很长时间才弄清楚它,即使现在我可能还没有找到最好的解决方案,我可能会在某个时候创建​​一个关于我的方法的教程,因为它更容易在你编码时解释它。
【解决方案2】:

编辑(2020 年 3 月 3 日):

让我们从头开始。

  1. 我创建了一个新项目:

    yarn create react-app firebase-hook-issue

  2. 我已经删除了默认创建的所有 3 个 App* 文件,从 index.js 中删除了导入,还删除了 service worker 以拥有一个干净的 index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

const App = () => {
    return (
        <div>
            Hello Firebase!            
        </div>
    );
};

ReactDOM.render(<App />, document.getElementById('root'));
  1. 我已启动应用程序,看到 Hello Firebase! 已打印出来。
  2. 我添加了 firebase 模块
yarn add firebase
  1. 我已经运行了 firebase init 来为该项目设置 firebase。我选择了一个空的 firebase 项目,并选择了 Database 和 Firestore,它们最终会创建下一个文件:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. 我为 firebase 库添加了导入,还创建了一个 Firebase 类和 FirebaseContext。最后,我将应用程序包装在 FirebaseContext.Provider 组件中,并将其值设置为 new Firebase() 实例。这是我们应该只实例化一个 Firebase 应用实例,因为它必须是一个单例:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

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

class Firebase {
    constructor() {
        app.initializeApp(firebaseConfig);

        this.realtimedb = app.database();
        this.firestore = app.firestore();
    }
}

const FirebaseContext = React.createContext(null);

const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

const App = () => {
    return <div>Hello Firebase!</div>;
};

ReactDOM.render(
    <FirebaseContext.Provider value={new Firebase()}>
        <App />
    </FirebaseContext.Provider>
    , document.getElementById("root"));
  1. 让我们验证我们是否可以从 Firestore 中读取任何内容。为了首先验证读数,我在 Firebase 控制台中访问我的项目,打开我的 Cloud Firestore 数据库并添加一个名为 counters 的新集合,其中包含一个名为的文档 simple value 类型为 number 且值为 0。

  2. 然后我更新了 App 类以使用我们创建的 FirebaseContext,为我们的简单计数器钩子制作了 useState 钩子,并使用 useEffect 钩子从 firestore 中读取值:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

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

const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

class Firebase {
    constructor() {
        app.initializeApp(firebaseConfig);

        this.realtimedb = app.database();
        this.firestore = app.firestore();
    }
}

const FirebaseContext = React.createContext(null);

const App = () => {
    const firebase = React.useContext(FirebaseContext);
    const [counter, setCounter] = React.useState(-1);

    React.useEffect(() => {
        firebase.firestore.collection("counters").doc("simple").get().then(doc => {
            if(doc.exists) {
                const data = doc.data();
                setCounter(data.value);
            } else {
                console.log("No such document");
            }
        }).catch(e => console.error(e));
    }, []);

    return <div>Current counter value: {counter}</div>;
};

ReactDOM.render(
    <FirebaseContext.Provider value={new Firebase()}>
        <App />
    </FirebaseContext.Provider>
    , document.getElementById("root"));

注意:为了使答案尽可能简短,我通过将对 firestore 的访问设置为测试模式(firestore.rules 文件)来确保您不需要通过 firebase 进行身份验证:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // This rule allows anyone on the internet to view, edit, and delete
    // all data in your Firestore database. It is useful for getting
    // started, but it is configured to expire after 30 days because it
    // leaves your app open to attackers. At that time, all client
    // requests to your Firestore database will be denied.
    //
    // Make sure to write security rules for your app before that time, or else
    // your app will lose access to your Firestore database
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2020, 4, 8);
    }
  }
}

我之前的回答: 非常欢迎您查看我的 react-firebase-auth-skeleton:

https://github.com/PompolutZ/react-firebase-auth-skeleton

主要是跟文:

https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial

但有些重写以使用钩子。我至少在两个项目中使用过它。

我当前宠物项目的典型用法:

import React, { useState, useEffect, useContext } from "react";
import ButtonBase from "@material-ui/core/ButtonBase";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
import { FirebaseContext } from "../../../firebase";
import { useAuthUser } from "../../../components/Session";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles(theme => ({
    root: {
        flexGrow: 1,
        position: "relative",
        "&::-webkit-scrollbar-thumb": {
            width: "10px",
            height: "10px",
        },
    },

    itemsContainer: {
        position: "absolute",
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        display: "flex",
        alignItems: "center",
        overflow: "auto",
    },
}));

export default function LethalHexesPile({
    roomId,
    tokens,
    onSelectedTokenChange,
}) {
    const classes = useStyles();
    const myself = useAuthUser();
    const firebase = useContext(FirebaseContext);
    const pointyTokenBaseWidth = 95;
    const [selectedToken, setSelectedToken] = useState(null);

    const handleTokenClick = token => () => {
        setSelectedToken(token);
        onSelectedTokenChange(token);
    };

    useEffect(() => {
        console.log("LethalHexesPile.OnUpdated", selectedToken);
    }, [selectedToken]);

    const handleRemoveFromBoard = token => e => {
        console.log("Request remove token", token);
        e.preventDefault();
        firebase.updateBoardProperty(roomId, `board.tokens.${token.id}`, {
            ...token,
            isOnBoard: false,
            left: 0,
            top: 0,
            onBoard: { x: -1, y: -1 },
        });
        firebase.addGenericMessage2(roomId, {
            author: "Katophrane",
            type: "INFO",
            subtype: "PLACEMENT",
            value: `${myself.username} removed lethal hex token from the board.`,
        });
    };

    return (
        <div className={classes.root}>
            <div className={classes.itemsContainer}>
                {tokens.map(token => (
                    <div
                        key={token.id}
                        style={{
                            marginRight: "1rem",
                            paddingTop: "1rem",
                            paddingLeft: "1rem",
                            filter:
                            selectedToken &&
                            selectedToken.id === token.id
                                ? "drop-shadow(0 0 10px magenta)"
                                : "",
                            transition: "all .175s ease-out",
                        }}
                        onClick={handleTokenClick(token)}
                    >
                        <div
                            style={{
                                width: pointyTokenBaseWidth * 0.7,
                                position: "relative",
                            }}
                        >
                            <img
                                src={`/assets/tokens/lethal.png`}
                                style={{ width: "100%" }}
                            />
                            {selectedToken && selectedToken.id === token.id && (
                                <ButtonBase
                                    style={{
                                        position: "absolute",
                                        bottom: "0%",
                                        right: "0%",
                                        backgroundColor: "red",
                                        color: "white",
                                        width: "2rem",
                                        height: "2rem",
                                        borderRadius: "1.5rem",
                                        boxSizing: "border-box",
                                        border: "2px solid white",
                                    }}
                                    onClick={handleRemoveFromBoard(token)}
                                >
                                    <DeleteIcon
                                        style={{
                                            width: "1rem",
                                            height: "1rem",
                                        }}
                                    />
                                </ButtonBase>
                            )}
                        </div>
                        <Typography>{`${token.id}`}</Typography>
                    </div>
                ))}
            </div>
        </div>
    );
}

这里有两个最重要的部分: - useAuthUser() 钩子,提供当前经过身份验证的用户。 - 我通过 useContext 钩子使用的 FirebaseContext。

const firebase = useContext(FirebaseContext);

当您有 firebase 的上下文时,您可以根据自己的喜好来实现 firebase 对象。有时我会编写一些有用的函数,有时在我为当前组件创建的 useEffect 挂钩中设置监听器会更容易。

那篇文章最好的部分之一是创建 withAuthorization HOC,它允许您在组件本身中指定访问页面的先决条件:

const condition = authUser => authUser && !!authUser.roles[ROLES.ADMIN];
export default withAuthorization(condition)(AdminPage);

或者甚至可以在您的路由器实现中设置这些条件。

希望查看 repo 和文章会给您一些额外的好想法,以增强您问题的其他精彩答案。

【讨论】:

  • 我买了他的书并按照他的方法。我发现条件方法在实施时实际上并没有起作用,并且那本书中规定的身份验证协议未能通过组件更新来保持状态。我还没有找到一种方法来使用那本书中的内容。无论如何,感谢您分享您的想法。
  • 我不确定你的意思。您是否在您的 firebase 项目中尝试过我的骨架应用程序?据我所知,所有条件都有效,因为我至少在 3 个项目中使用过它。
【解决方案3】:

Firebase 未定义,因为您没有导入它。首先,它需要是firebase.firestore(),如您链接到https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore 的文档中的示例所示。然后你需要在文件中实际导入firebase,所以import * as firebase from 'firebase';如firebase包自述文件中所述https://www.npmjs.com/package/firebase

【讨论】:

  • 我在 index.js 中导入
  • ReactDOM.render( , document.getElementById('root') );
  • 这就是该方法适用于 componentDidMount 的原因
  • withAuth HOC 也包含在 withFirebase 中。
  • 但是你的错误说它是未定义的。我正在帮助您定义它,您并没有告诉我解决方案是否有效或导致的错误是什么。你总是很难帮助你,梅尔。我的意思是您将它导入到dashboard2组件文件以外的文件中,这是您引用它的位置,这导致了错误。 Imorting 索引中的某些内容并不能帮助您的构建了解它在完全不同的文件中的内容。
猜你喜欢
  • 2021-03-30
  • 2019-11-06
  • 2018-06-20
  • 1970-01-01
  • 2019-07-09
  • 1970-01-01
  • 2020-07-08
  • 1970-01-01
  • 2020-10-17
相关资源
最近更新 更多