Trình nghe Firebase với React Hook


27

Tôi đang cố gắng tìm ra cách sử dụng trình nghe Firebase để dữ liệu của đám mây được làm mới với các cập nhật móc phản ứng.

Ban đầu, tôi đã thực hiện điều này bằng cách sử dụng một thành phần lớp với hàm thành phầnDidMount để lấy dữ liệu đầu tiên.

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,
  });  
}

Điều đó bị phá vỡ khi trang cập nhật, vì vậy tôi đang cố gắng tìm ra cách di chuyển người nghe để phản ứng móc.

Tôi đã cài đặt công cụ hook-firebase-hook - mặc dù tôi không thể tìm ra cách đọc hướng dẫn để có thể làm cho nó hoạt động.

Tôi có một thành phần chức năng như sau:

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);

Thành phần này được bọc trong một trình bao bọc authUser ở cấp tuyến đường như sau:

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

Tôi có một tệp firebase.js, cắm vào Firestore như sau:

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


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


  }

Nó cũng định nghĩa một người nghe để biết khi nào authUser thay đổi:

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;
        }
    });
  };

Sau đó, trong thiết lập bối cảnh firebase của tôi, tôi có:

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

Bối cảnh được thiết lập trong trình bao bọc withFirebase như sau:

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;

Sau đó, trong HOCentent HOC của tôi, tôi có một nhà cung cấp ngữ cảnh như:

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;

Hiện tại - khi tôi thử điều này, tôi gặp lỗi trong thành phần Dashboard2 có nội dung:

Firebase 'không được xác định

Tôi đã thử firebase chữ thường và nhận được cùng một lỗi.

Tôi cũng đã thử firebase.firestore và Firebase.firestore. Tôi nhận được lỗi tương tự.

Tôi tự hỏi nếu tôi không thể sử dụng HOC của tôi với một thành phần chức năng?

Tôi đã thấy ứng dụng demo này và bài đăng trên blog này .

Theo lời khuyên trong blog, tôi đã tạo một firebase / contextReader.jsx mới với:

 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
  }  

Sau đó, tôi cố gắng bọc App.jsx của mình trong trình đọc đó bằng:

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} />

Khi tôi thử điều này, tôi nhận được một lỗi cho biết:

LoạiError: _firebase__weBPACK_IMPORTED_MODULE_2 __. Default.auth không phải là một chức năng

Tôi đã thấy bài này xử lý lỗi đó và đã thử gỡ cài đặt và cài đặt lại sợi. Nó không có Gì Thay đổi.

Khi tôi nhìn vào ứng dụng demo , nó gợi ý rằng bối cảnh nên được tạo bằng phương pháp 'giao diện'. Tôi không thể thấy điều này đến từ đâu - tôi không thể tìm thấy tài liệu tham khảo để giải thích nó trong tài liệu.

Tôi không thể hiểu được các hướng dẫn ngoài việc thử những gì tôi đã làm để cắm cái này vào.

Tôi đã thấy bài đăng này cố gắng lắng nghe Firestore mà không sử dụng hook-firebase-hook. Các câu trả lời quay trở lại để cố gắng tìm ra cách sử dụng công cụ này.

Tôi đã đọc lời giải thích tuyệt vời này trong đó làm thế nào để chuyển từ HOC sang hook. Tôi bị mắc kẹt với cách tích hợp trình nghe firebase.

Tôi đã thấy bài đăng này cung cấp một ví dụ hữu ích cho cách suy nghĩ về việc này. Không chắc chắn nếu tôi nên cố gắng làm điều này trong thành phần authListenerDidMount - hoặc trong thành phần Bảng điều khiển đang cố gắng sử dụng nó.

ATTEMPT TIẾP THEO Tôi tìm thấy bài đăng này , đang cố gắng giải quyết vấn đề tương tự.

Khi tôi cố gắng thực hiện giải pháp được cung cấp bởi Shubham Khatri, tôi đã thiết lập cấu hình firebase như sau:

Nhà cung cấp ngữ cảnh có: nhập React, {useContext} từ 'Reac'; nhập Firebase từ '../../firebase';

const FirebaseContext = React.createContext(); 

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

Các hook bối cảnh sau đó có:

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;

Sau đó, trong tệp index.js, tôi gói Ứng dụng trong nhà cung cấp là:

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();

Sau đó, khi tôi cố gắng sử dụng trình nghe trong thành phần tôi có:

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;

Và tôi cố gắng sử dụng nó như một tuyến đường không có thành phần hoặc trình bao bọc auth:

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

Khi tôi thử điều này, tôi nhận được một lỗi cho biết:

Lỗi nhập đã cố gắng: 'FirebaseContext' không được xuất từ ​​'../Firebase/ContextHookProvider'.

Thông báo lỗi đó có ý nghĩa, bởi vì ContextHookProvider không xuất FirebaseContext - nó xuất FirebaseProvider - nhưng nếu tôi không thử nhập cái này trong Dashboard2 - thì tôi không thể truy cập nó trong chức năng cố gắng sử dụng nó.

Một tác dụng phụ của nỗ lực này là phương thức đăng ký của tôi không còn hoạt động. Bây giờ nó tạo ra một thông báo lỗi cho biết:

LoạiError: Không thể đọc thuộc tính 'doCreateUserWithEmailAndPassword' của null

Tôi sẽ giải quyết vấn đề này sau - nhưng phải có cách tìm ra cách sử dụng phản ứng với căn cứ hỏa lực không liên quan đến nhiều tháng của vòng lặp này thông qua hàng triệu con đường không hoạt động để có được thiết lập xác thực cơ bản. Có một bộ khởi động cho firebase (Firestore) hoạt động với móc phản ứng không?

Nỗ lực tiếp theo tôi đã cố gắng làm theo cách tiếp cận trong khóa học udemy này - nhưng nó chỉ hoạt động để tạo đầu vào biểu mẫu - không có người nghe để đặt xung quanh các tuyến đường để điều chỉnh với người dùng được xác thực.

Tôi đã cố gắng làm theo cách tiếp cận trong hướng dẫn youtube này - có repo này để làm việc. Nó chỉ ra cách sử dụng hook, nhưng không sử dụng ngữ cảnh.

ATTEMPT TIẾP THEO Tôi tìm thấy repo này dường như có một cách tiếp cận được cân nhắc kỹ lưỡng khi sử dụng móc với Firestore. Tuy nhiên, tôi không thể hiểu được mã.

Tôi đã nhân bản cái này - và cố gắng thêm tất cả các tệp công khai và sau đó khi tôi chạy nó - tôi thực sự không thể lấy mã để hoạt động. Tôi không chắc những gì còn thiếu trong hướng dẫn về cách chạy cái này để xem liệu có bài học nào trong mã có thể giúp giải quyết vấn đề này không.

TIẾP THEO TIẾP THEO

Tôi đã mua mẫu divjoy, được quảng cáo là đang được thiết lập cho căn cứ hỏa lực (nó không được thiết lập cho Firestore trong trường hợp bất kỳ ai khác đang coi đây là một tùy chọn).

Mẫu đó đề xuất một trình bao bọc xác thực khởi tạo cấu hình của ứng dụng - nhưng chỉ dành cho các phương thức xác thực - vì vậy nó cần phải được cấu trúc lại để cho phép nhà cung cấp ngữ cảnh khác thực hiện. Khi bạn thực hiện quá trình đó và sử dụng quy trình được hiển thị trong bài đăng này , những gì còn lại là một lỗi trong cuộc gọi lại sau:

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

Nó không biết căn cứ hỏa lực là gì. Đó là bởi vì nó được xác định trong nhà cung cấp bối cảnh firebase được nhập và định nghĩa (trong hàm useProvideAuth ()) là:

  const firebase = useContext(FirebaseContext)

Không có cơ hội gọi lại, lỗi nói:

Sử dụng React HookEffect có một phụ thuộc bị thiếu: 'firebase'. Bao gồm nó hoặc loại bỏ mảng phụ thuộc

Hoặc, nếu tôi thử và thêm const đó vào cuộc gọi lại, tôi sẽ gặp lỗi:

React Hook "useContext" không thể được gọi trong cuộc gọi lại. React Hook phải được gọi trong thành phần chức năng React hoặc chức năng React Hook tùy chỉnh

TIẾP THEO TIẾP THEO

Tôi đã giảm tập tin cấu hình firebase của mình xuống chỉ còn các biến cấu hình (tôi sẽ viết các trình trợ giúp trong các nhà cung cấp ngữ cảnh cho từng bối cảnh tôi muốn sử dụng).

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;  

Sau đó tôi có một nhà cung cấp bối cảnh xác thực như sau:

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];
};

Tôi cũng đã thực hiện một nhà cung cấp bối cảnh firebase như sau:

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>
    )
  }

Sau đó, trong index.js tôi gói ứng dụng trong nhà cung cấp firebase

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

serviceWorker.unregister();

và trong danh sách các tuyến đường của tôi, tôi đã gói các tuyến có liên quan trong nhà cung cấp xác thực:

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;

Trong nỗ lực cụ thể này, tôi trở lại vấn đề được gắn cờ trước đó với lỗi này:

LoạiError: _firebase__weBPACK_IMPORTED_MODULE_2 __. Default.auth không phải là một chức năng

Nó chỉ ra dòng này của nhà cung cấp xác thực khi tạo ra vấn đề:

useEffect(() => {

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

Tôi đã thử sử dụng chữ F viết hoa trong Firebase và nó cũng tạo ra lỗi tương tự.

Khi tôi thử lời khuyên của Tristan, tôi loại bỏ tất cả những điều đó và thử và xác định phương thức hủy đăng ký của mình là phương pháp không niêm yết (tôi không biết tại sao anh ta không sử dụng ngôn ngữ firebase - nhưng nếu cách tiếp cận của anh ta hiệu quả, tôi sẽ cố gắng hơn để tìm hiểu tại sao). Khi tôi cố gắng sử dụng giải pháp của mình, thông báo lỗi cho biết:

LoạiError: _util_contexts_Firebase__weBPACK_IMPORTED_MODULE_8 ___ mặc định (...) không phải là một chức năng

Câu trả lời cho bài đăng này đề nghị xóa () khỏi sau auth. Khi tôi thử điều đó, tôi nhận được một lỗi cho biết:

LoạiError: Không thể đọc thuộc tính 'onAuthStateChanged' không xác định

Tuy nhiên bài đăng này cho thấy một vấn đề với cách nhập căn cứ hỏa lực trong tệp xác thực.

Tôi đã nhập nó dưới dạng: nhập Firebase từ "../firebase";

Firebase là tên của lớp.

Các video mà Tristan đề xuất là bối cảnh hữu ích, nhưng tôi hiện đang ở tập 9 và vẫn chưa tìm thấy phần nào được cho là giúp giải quyết vấn đề này. Có ai biết nơi để tìm thấy điều đó?

TIẾP THEO TIẾP THEO Tiếp theo - và chỉ cố gắng giải quyết vấn đề bối cảnh - Tôi đã nhập cả createdContext và useContext và cố gắng sử dụng chúng như trong tài liệu này.

Tôi không thể vượt qua một lỗi cho biết:

Lỗi: Cuộc gọi hook không hợp lệ. Móc chỉ có thể được gọi bên trong cơ thể của một thành phần chức năng. Điều này có thể xảy ra vì một trong những lý do sau: ...

Tôi đã thông qua các đề xuất trong liên kết này để thử và giải quyết vấn đề này và không thể tìm ra nó. Tôi không có bất kỳ vấn đề nào trong hướng dẫn chụp rắc rối này.

Hiện tại - tuyên bố bối cảnh trông như sau:

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>
  );  

Tôi đã dành thời gian sử dụng khóa học udemy này để thử và tìm ra bối cảnh và móc phần tử cho vấn đề này - sau khi xem nó - khía cạnh duy nhất cho giải pháp được Tristan đề xuất dưới đây là phương thức createContext không được gọi chính xác trong bài đăng của anh ấy. nó cần phải là "React.createContext" nhưng nó vẫn không thể giải quyết vấn đề.

Tôi vẫn bị mắc kẹt.

Bất cứ ai cũng có thể thấy những gì đã đi lạc ở đây?


Nó không được xác định bởi vì bạn không nhập nó.
Josh Pittman

3
bạn chỉ cần thêm exportvào export const FirebaseContext?
Federkun

Xin chào Mel xin lỗi vì trả lời chậm, tôi đã có hai tuần làm việc điên rồ nên việc nhìn vào máy tính vào buổi tối là điều không thể! Tôi đã cập nhật câu trả lời của tôi cho bạn để cung cấp một giải pháp sạch và rất đơn giản mà bạn có thể kiểm tra.
Huấn luyện viên Tristan

Cảm ơn rất nhiều - tôi sẽ xem xét nó ngay bây giờ.
Mel

Này Mel, chỉ cần cập nhật thêm với việc triển khai chính xác các cập nhật theo Thời gian thực từ Firestore (có thể xóa phần onSnapshot để làm cho nó không phải là thời gian thực) Nếu đây là giải pháp, tôi có thể đề nghị có thể cập nhật câu hỏi của bạn để làm cho nó thành một ngắn hơn và ngắn gọn hơn để những người khác xem nó cũng có thể tìm ra giải pháp, cảm ơn - xin lỗi lần nữa vì bản chất phản hồi chậm
Huấn luyện viên Tristan

Câu trả lời:


12

Chỉnh sửa chính : Mất một thời gian để xem xét thêm một chút, đây là điều tôi nghĩ ra là một giải pháp sạch hơn, ai đó có thể không đồng ý với tôi về việc đây là một cách tốt để tiếp cận điều này.

Sử dụng Hook Auth Hook

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 }

Nếu authUser là null thì không được xác thực, nếu người dùng có giá trị thì được xác thực.

firebaseConfigcó thể được tìm thấy trên Bảng điều khiển firebase => Cài đặt dự án => Ứng dụng => Cấu hình nút radio

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

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

Móc sử dụngEffect này là cốt lõi để theo dõi các xác thực của người dùng. Chúng tôi thêm một trình lắng nghe vào sự kiện onAuthStateChanged của firebase.auth () cập nhật giá trị của authUser. Phương thức này trả về một cuộc gọi lại để hủy đăng ký trình nghe này mà chúng ta có thể sử dụng để dọn sạch trình nghe khi hook useFirebase được làm mới.

Đây là cái móc duy nhất chúng ta cần để xác thực căn cứ hỏa lực (những cái móc khác có thể được tạo ra cho Firestore, v.v.

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>
}

Đây là một thực hiện cơ bản của các Appthành phần của mộtcreate-react-app

cơ sở dữ liệu hookFirestore

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 }
}

Điều này có thể được thực hiện trong thành phần của bạn như vậy:

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" }
  );

}

Điều này sau đó loại bỏ sự cần thiết của React useContext khi chúng tôi nhận được các bản cập nhật trực tiếp từ chính căn cứ hỏa lực.

Lưu ý một vài điều:

  1. Lưu tài liệu không thay đổi sẽ không kích hoạt ảnh chụp nhanh mới để "vượt mức" không gây ra rerenders
  2. Khi gọi getDocument, cuộc gọi lại onUpdate được gọi ngay lập tức với một "ảnh chụp nhanh" ban đầu, do đó bạn không cần thêm mã để có được trạng thái ban đầu của tài liệu.

Chỉnh sửa đã loại bỏ một đoạn lớn của câu trả lời cũ


1
cám ơn vì cái này. Tôi không thể thấy cách bạn thiết lập điều này. Với nhà cung cấp, tôi nhận được một lỗi cho biết createdContext không được xác định. Đó là bởi vì không có người tiêu dùng cho đến nay. Bạn đã đặt của bạn ở đâu?
Mel

Xin lỗi, tạoContContext là một phần của phản ứng, vì vậy hãy nhập nó ở đầu dưới dạng {createdContext} từ 'Reac' Tôi nhận ra rằng tôi đã quên hiển thị nhà cung cấp Firebase đi đâu, tôi sẽ chỉnh sửa câu trả lời
Tristan Trainer

Tôi đã nhập nó trong nhà cung cấp, nhưng nó làm cho không xác định. Tôi nghĩ đó là bởi vì không có người tiêu dùng nào cho nó
Mel

1
Người tiêu dùng là hook useContext (), nhưng nhìn lại câu hỏi của bạn, có vẻ như bạn không xuất FirebaseContext từ tệp - đó là lý do tại sao nó không thể tìm thấy bối cảnh :)
Tristan Trainer

1
Xin chào @Mel cảm ơn bạn rất tốt bụng, tôi hy vọng cuối cùng nó cũng hữu ích. Cả Hook và Firebase đều khá liên quan và tôi đã mất một thời gian dài để xoay quanh nó và tôi có thể không tìm thấy giải pháp tốt nhất ngay cả bây giờ, tôi có thể tạo một hướng dẫn về cách tiếp cận của mình một lúc nào đó vì nó dễ giải thích hơn khi bạn viết mã nó
Huấn luyện viên Tristan

4

Firebase không được xác định bởi vì bạn không nhập nó. Đầu tiên, cần phải được thể firebase.firestore()hiện trong ví dụ về các tài liệu bạn đã liên kết với https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Sau đó, bạn cần phải thực sự nhập firebase trong tệp, import * as firebase from 'firebase';như được nêu trong gói firebase readme https://www.npmjs.com/package/firebase


1
Tôi đang nhập nó trong index.js
Mel

1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getEuityById ('root'));
Mel

1
Đó là lý do cách tiếp cận hoạt động với thành
phầnDidMount

1
WithAuth HOC cũng được bọc trongFirebase.
Mel

3
Nhưng lỗi của bạn nói rằng nó không được xác định. Tôi đang giúp bạn xác định nó và bạn sẽ không cho tôi biết nếu giải pháp hoạt động hay lỗi kết quả là gì. Bạn luôn làm cho nó rất khó để giúp bạn Mel. Quan điểm của tôi là bạn đang nhập nó trong một tệp khác với tệp thành phần dashboard2, nơi bạn tham chiếu nó, điều này gây ra lỗi. Việc loại bỏ thứ gì đó trong chỉ mục không giúp bản dựng của bạn hiểu được nó trong một tệp hoàn toàn khác.
Josh Pittman

2

EDIT (ngày 3 tháng 3 năm 2020):

Hãy bắt đầu lại từ đầu.

  1. Tôi đã tạo một dự án mới:

    sợi tạo ra phản ứng ứng dụng firebase-hook-vấn đề

  2. Tôi đã xóa tất cả 3 tệp Ứng dụng * được tạo theo mặc định, đã xóa nhập từ index.js và cũng xóa nhân viên dịch vụ để có một index.js sạch như thế:

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. Tôi đã khởi động ứng dụng để thấy Hello Firebase! được in ra.
  2. Tôi đã thêm mô-đun firebase
yarn add firebase
  1. Tôi đã chạy init firebase để thiết lập firebase cho dự án đó. Tôi đã chọn một trong những dự án căn cứ hỏa lực trống của mình và tôi đã chọn Cơ sở dữ liệu và Firestore, cuối cùng tạo ra các tệp tiếp theo:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Tôi đã thêm nhập khẩu cho libs căn cứ hỏa lực và cũng tạo ra một căn cứ hỏa lực lớp và FirebaseContext . Cuối cùng, tôi đã gói Ứng dụng trong thành phần FirebaseContext.Provider và đặt giá trị của nó thành một phiên bản Firebase () mới . Đây là chúng ta sẽ chỉ có một phiên bản ứng dụng Firebase được khởi tạo như chúng ta nên bởi vì nó phải là một singleton:
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. Hãy xác minh rằng chúng ta có thể đọc bất cứ thứ gì từ Firestore. Để xác minh việc đọc đầu tiên, tôi đã đi đến dự án của mình trong Bảng điều khiển Firebase, mở cơ sở dữ liệu Cloud Firestore của tôi và thêm một bộ sưu tập mới gọi là bộ đếm với một tài liệu đơn giản chứa một trường gọi là giá trị của loại số và giá trị 0. nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

  2. Sau đó, tôi đã cập nhật lớp Ứng dụng để sử dụng FirebaseContext mà chúng tôi đã tạo, đã sử dụng hook useState cho hook hook đơn giản của chúng tôi và sử dụng hook useEffect để đọc giá trị từ 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"));

Lưu ý: Để giữ câu trả lời càng ngắn càng tốt Tôi đã đảm bảo rằng bạn không cần phải được xác thực bằng căn cứ hỏa lực, bằng cách đặt quyền truy cập vào vị trí cứu hỏa ở chế độ thử nghiệm (tệp Firestore.rules):

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);
    }
  }
}

Câu trả lời trước của tôi: Bạn được chào đón nhiều hơn khi xem qua phản ứng-firebase-auth-skeleton của tôi:

https://github.com/PompolutZ/react-firebase-auth-sk MP

Nó chủ yếu theo sau bài viết:

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

Nhưng phần nào viết lại để sử dụng móc. Tôi đã sử dụng nó trong ít nhất hai dự án của mình.

Sử dụng điển hình từ dự án thú cưng hiện tại của tôi:

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>
    );
}

Hai phần quan trọng nhất ở đây là: - hook useAuthUser () cung cấp người dùng được xác thực hiện tại. - FirebaseContext mà tôi sử dụng thông qua hook useContext .

const firebase = useContext(FirebaseContext);

Khi bạn có bối cảnh cho căn cứ hỏa lực, tùy thuộc vào bạn để triển khai đối tượng căn cứ hỏa lực theo ý thích của bạn. Đôi khi tôi viết một số chức năng hữu ích, đôi khi việc cài đặt trình nghe ngay trong hook useEffect tôi tạo cho thành phần hiện tại của tôi dễ dàng hơn .

Một trong những phần hay nhất của bài viết đó là việc tạo ra withAuthorization HOC, cho phép bạn chỉ định các điều kiện tiên quyết để truy cập trang hoặc trong chính thành phần:

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

Hoặc thậm chí có thể thiết lập các điều kiện đó ngay trong quá trình thực hiện bộ định tuyến của bạn.

Hy vọng rằng nhìn vào repo và bài viết sẽ cung cấp cho bạn một số suy nghĩ tốt để tăng cường các câu trả lời xuất sắc khác cho câu hỏi của bạn.


Tôi mua cuốn sách của anh ấy và làm theo cách tiếp cận của anh ấy. Tôi thấy cách tiếp cận điều kiện không thực sự hoạt động khi được triển khai và giao thức auth được nêu trong cuốn sách đó không thể duy trì trạng thái thông qua các bản cập nhật thành phần. Tôi chưa tìm được cách sử dụng những gì được nêu trong cuốn sách đó. Dù sao cũng cảm ơn vì đã chia sẻ suy nghĩ của bạn.
Mel

Tôi không chắc ý của bạn là gì. Bạn đã thử ứng dụng bộ xương của tôi với dự án firebase của bạn chưa? Tất cả các điều kiện hoạt động như tôi có thể nói, vì tôi đã sử dụng nó trong ít nhất 3 dự án.
fxdxpz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.