Firebase слушатель с React Hooks - PullRequest
       20

Firebase слушатель с React Hooks

27 голосов
/ 30 января 2020

Я пытаюсь понять, как использовать прослушиватель Firebase, чтобы данные облачного пожарного хранилища обновлялись с помощью обновлений активных перехватчиков.

Изначально я сделал это, используя компонент класса с функцией componentDidMount для получения данных о хранилище.

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

Это ломается при обновлении страницы, поэтому я пытаюсь выяснить, как переместите слушателя, чтобы реагировать на ловушки.

Я установил инструмент response-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 HO C у меня есть поставщик контекста как :

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. Я получаю ту же ошибку.

Мне интересно, не могу ли я использовать свой HO C с функциональным компонентом?

Я видел это демонстрационное приложение и это сообщение в блоге .

Следуя совету в блоге, я создал новый 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 не является функцией

Я видел это сообщение имеет дело с этой ошибкой и попытался удалить и переустановить пряжу. Это не имеет значения.

Когда я смотрю на демонстрационное приложение , оно предлагает создать контекст с использованием метода 'interface'. Я не вижу, откуда это исходит - я не могу найти ссылку, чтобы объяснить это в документации.

Я не могу понять смысл инструкций, кроме как попробовать то, что я сделал, чтобы подключить this in.

Я видел этот пост , который пытается прослушать firestore без использования активных-firebase-hooks. Ответы указывают на попытку выяснить, как использовать этот инструмент.

Я прочитал это превосходное объяснение , в котором рассказывается, как перейти от HOC к хукам. Я застрял с тем, как интегрировать слушатель Firebase.

Я видел этот пост , который предоставляет полезный пример того, как думать об этом. Не уверен, стоит ли мне пытаться сделать это в компоненте authListenerDidMount - или в компоненте Dashboard, который пытается его использовать.

СЛЕДУЮЩАЯ ПОПЫТКА Я нашел этот пост , который пытается решить ту же проблему.

Когда я пытаюсь реализовать решение, предлагаемое Шубхамом Кхатри, я настраивал конфигурацию firebase следующим образом:

Поставщик контекста с: import React, {useContext} из 'Reaction'; импортировать 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;

Затем в индексе. 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;

И я пытаюсь использовать его как маршрут без компонентов или auth wrapper:

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

Когда я пытаюсь это сделать, я получаю сообщение об ошибке:

Попытка ошибки импорта: «FirebaseContext» не экспортируется из «../Firebase/». ContextHookProvider '.

Это сообщение об ошибке имеет смысл, потому что ContextHookProvider не экспортирует FirebaseContext - он экспортирует FirebaseProvider - но если я не пытаюсь импортировать его в Dashboard2 - тогда я не могу получить к нему доступ в функции, которая пытается его использовать.

Одним из побочных эффектов этой попытки является то, что мой метод регистрации больше не работает. Теперь генерируется сообщение об ошибке:

TypeError: Невозможно прочитать свойство 'doCreateUserWithEmailAndPassword' со значением null

Я решу эту проблему позже, но должна быть способ выяснить, как использовать реакцию с firebase, которая не включает месяцы этого l oop через миллионы путей, которые не работают, чтобы получить базовую c настройку аутентификации. Есть ли стартовый набор для firebase (firestore), который работает с реактивными крючками?

Следующая попытка Я пытался следовать подходу в этом курсе udemy - но это только работает для генерации ввода формы - нет слушателя, который бы разбирал маршруты для настройки с аутентифицированным пользователем.

Я пытался следовать подходу в этом руководстве по YouTube - которое имеет это репо для работы с. Он показывает, как использовать хуки, но не как использовать контекст.

СЛЕДУЮЩАЯ ПОПЫТКА Я нашел это репозиторий , который, кажется, имеет хорошо продуманный подход к использованию хуков с пожарным. Тем не менее, я не могу понять код.

Я клонировал это - и попытался добавить все файлы publi c, а затем, когда я его запустил - я не могу заставить код работать. Я не уверен, чего не хватает в инструкциях о том, как заставить это работать, чтобы увидеть, есть ли уроки в коде, которые могут помочь решить эту проблему.

СЛЕДУЮЩАЯ ПОПЫТКА

Я купил шаблон divjoy, который объявлен как установка для firebase (это не установка для firestore в случае, если кто-то еще рассматривает это как вариант).

Этот шаблон предлагает аутентификацию Оболочка, которая инициализирует конфигурацию приложения - но только для методов auth - поэтому ее необходимо реструктурировать, чтобы позволить другому провайдеру контекста для firestore. Когда вы запутываетесь в этом процессе и используете процесс, показанный в этом посте , то остается ошибка в следующем обратном вызове:

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 "не может быть вызвано внутри обратного вызова. React Hooks должны вызываться в компоненте функции React или в пользовательской функции React Hook

СЛЕДУЮЩАЯ ПОПЫТКА

Я сократил файл конфигурации 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);
      }
    });

Я попытался использовать заглавную букву F в Firebase, и она генерирует то же самое Эрро т.

Когда я пользуюсь советом Тристана, я удаляю все эти вещи и пытаюсь определить свой метод отказа от подписки как метод отсутствия доступа (я не знаю, почему он не использует язык Firebase - но если его подход сработал, я ' буду стараться выяснить почему). Когда я пытаюсь использовать его решение, появляется сообщение об ошибке:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) не является функцией

Ответ это post предлагает удалить () после auth. Когда я пытаюсь это сделать, я получаю сообщение об ошибке:

TypeError: Невозможно прочитать свойство 'onAuthStateChanged' из неопределенного

Однако этот пост предлагает проблема с тем, как firebase импортируется в файл аутентификации.

У меня он импортирован как: импорт Firebase из "../firebase";

Firebase - это имя класса.

Видео, рекомендованные Тристаном, являются полезным фоном, но я в настоящее время нахожусь в эпизоде ​​9 и все еще не нашел ту часть, которая должна помочь решить эту проблему. Кто-нибудь знает, где это найти?

СЛЕДУЮЩАЯ ПОПЫТКА Далее - и пытаясь решить только проблему контекста - я импортировал оба createContext и useContext и попытался использовать их, как показано в этой документации .

Я не могу передать ошибку, которая говорит:

Ошибка: недопустимый вызов ловушки. Хуки могут быть вызваны только внутри тела компонента функции. Это может произойти по одной из следующих причин: ...

Я прошел через предложения в по этой ссылке , чтобы попытаться решить эту проблему, и не могу ее решить. У меня нет проблем, показанных в этом руководстве по устранению неполадок.

В настоящее время - контекстное утверждение выглядит следующим образом:

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 чтобы попытаться выяснить контекст и подключить элемент к этой проблеме - после просмотра - единственным аспектом решения, предложенного Тристаном ниже, является то, что метод createContext не вызывается правильно в его посте. это должен быть «React.createContext», но он все еще не подходит для решения проблемы.

Я все еще застрял.

Может кто-нибудь увидеть, что здесь не так?

Ответы [ 3 ]

12 голосов
/ 02 февраля 2020

Major Edit : Потребовалось некоторое время, чтобы разобраться в этом немного подробнее. Это то, что я придумал, это более чистое решение, кто-то может не согласиться со мной по поводу того, что это хороший способ приблизиться к этому.

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

Если authUser имеет значение null, то не аутентифицируется, если у пользователя есть значение, то аутентифицируется.

firebaseConfig можно найти на базе Firebase Консоль => Настройки проекта => Приложения => Config Radio Button

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

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

Этот хук useEffect является ядром для отслеживания изменений пользователя. Мы добавляем слушатель к событию onAuthStateChanged из firebase.auth (), который обновляет значение authUser. Этот метод возвращает обратный вызов для отмены подписки этого слушателя, который мы можем использовать для очистки слушателя при обновлении хука useFirebase.

Это единственный хук, который нам нужен для аутентификации в firebase (другие хуки могут быть сделаны для firestore et c.

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

Это базовая c реализация App компонента create-react-app

useFirestore Hook базы данных

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 вызывается сразу с начальным «снимком», поэтому вам не нужен дополнительный код для получения начального состояния документа.

Редактирование удалено arge кусок старого ответа

4 голосов
/ 30 января 2020

Firebase не определен, потому что вы не импортируете его. Во-первых, он должен быть firebase.firestore(), как показано в примере на документах, которые вы связали с https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore. Затем вам нужно импортировать firebase в файл, поэтому import * as firebase from 'firebase';, как указано в readme пакета firebase https://www.npmjs.com/package/firebase

2 голосов
/ 03 марта 2020

РЕДАКТИРОВАТЬ (3 марта 2020 г.):

Давайте начнем с нуля.

  1. Я создал новый проект:

    пряжа создать ответное приложение firebase-hook-issue

  2. Я удалил все 3 файла App *, созданные по умолчанию, удалил импорт из индекса. js, а также удалил у работника службы должен быть чистый индекс. 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'));
Я запустил приложение, чтобы увидеть, что Hello Firebase! распечатано. Я добавил модуль Firebase
yarn add firebase
Я запустил firebase init, чтобы настроить firebase для этого проекта. Я выбрал один из моих пустых проектов Firebase и выбрал Database и Firestore, которые в итоге создают следующие файлы:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
Я добавил импорт для библиотек Firebase, а также создал класс Firebase и FirebaseContext . Наконец, я обернул приложение в FirebaseContext.Provider компонент и установил его значение в новый экземпляр 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"));

Давайте проверим, что мы можем читать что угодно из Firestore. Чтобы проверить только чтение, я пошел в свой проект в Firebase Console, открыл свою базу данных Cloud Firestore и добавил новую коллекцию под названием counters с документом simple , содержащим одно поле с именем значение номера типа и значения 0. enter image description here enter image description here

Затем я обновил класс приложения до использовать созданный нами FirebaseContext, использовать хук useState для нашего простого счетчика хуков и использовать хук useEffect для чтения значения из пожарного хранилища:

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

Примечание. Возможно, я убедился, что вам не нужно проходить проверку подлинности с помощью firebase, установив доступ к пожарному хранилищу в тестовом режиме (файл 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);
    }
  }
}

Мой предыдущий ответ : Приглашаем вас взглянуть на мой реактив-firebase-auth-скелет:

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

Это в основном следует статье:

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

Но несколько переписать п использовать крючки. Я использовал его по крайней мере в двух моих проектах.

Типичное использование из моего текущего проекта Pet:

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 () хук, который предоставляет текущего аутентифицированного пользователя - FirebaseContext, который я использую через useContext hook.

const firebase = useContext(FirebaseContext);

Когда у вас есть контекст для firebase, вы можете реализовать объект firebase по своему вкусу. Иногда я пишу некоторые полезные функции, иногда проще просто настроить слушателей прямо в хуке useEffect , который я создаю для моего текущего компонента.

Одной из лучших частей этой статьи было создание withAuthorization HO C, который позволяет указать предварительные условия для доступа к странице либо в самом компоненте:

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

Или, возможно, даже настроить эти условия прямо в реализации вашего маршрутизатора.

Надеюсь, что просмотр репозитория и статьи даст вам несколько хороших мыслей, чтобы улучшить другие блестящие ответы на ваш вопрос.

...