如何将默认反应上下文值设置为来自 Firestore 的数据?

Posted

技术标签:

【中文标题】如何将默认反应上下文值设置为来自 Firestore 的数据?【英文标题】:How to set the default react context value as data from firestore? 【发布时间】:2021-06-26 01:55:52 【问题描述】:

我正在构建一个锻炼计划计划器应用程序,锻炼计划在应用程序中使用 SetProgram 上下文进行处理,并使用名为 useProgram 的自定义挂钩进行更新。我需要当用户登录该应用程序将从 Firestore 获取数据并显示用户的锻炼计划时,我该怎么做?请记住,useProgram 挂钩也用于整个应用程序来编辑和更新一个人的锻炼计划。

App.tsx

import React,  useContext, useEffect, useState  from "react";
import  BrowserRouter as Router  from "react-router-dom";
import AppRouter from "./Router";
import FirebaseApp from "./firebase";

import SetProgram from "./context/program";

import  useProgram  from "./components/hooks/useProgram";
import firebaseApp from "./firebase/firebase";
import  useAuthState  from "react-firebase-hooks/auth";

function App() 
  const program = useProgram();

  const day = useDay();
  const [user, loading, error] = useAuthState(firebaseApp.auth);

  
  return (
    <div className="App">
      <SetProgram.Provider value=program>
                <Router>
                  <AppRouter />
                </Router>
      </SetProgram.Provider>
    </div>
  );


export default App;

firebase.ts

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

import firebaseConfig from "./config";


class Firebase 
  auth: firebase.auth.Auth;
  user: firebase.User | null | undefined;
  db: firebase.firestore.Firestore;
  userProgram:  | undefined;

  constructor() 
    firebase.initializeApp(firebaseConfig);
    this.auth = firebase.auth();
    this.db = firebase.firestore();
  

  async register() 
    if (this.user) 
      this.db.collection("users").doc(this.user.uid).set(
        name: this.user.displayName,
        email: this.user.email,
        userId: this.user.uid,
        program: ,
      );
    
  

  async getResults() 
    return await this.auth.getRedirectResult().then((results) => 
      console.log("results.user", results.user);
      if (!results.additionalUserInfo?.isNewUser) 
        this.getProgram();
       else 
        this.register();
      
    );
  

  async login(
    user: firebase.User | null | undefined,
    loading: boolean,
    error: firebase.auth.Error | undefined
  ) 
 
    const provider = new firebase.auth.GoogleAuthProvider();
    return await this.auth
      .signInWithRedirect(provider)
      .then(() => this.getResults());
  

  async logout() 
    return await this.auth.signOut().then(() => console.log("logged out"));
  

  async updateProgram(user: firebase.User, program: ) 
    if (this.userProgram !== program) 
      firebaseApp.db
        .collection("users")
        .doc(user.uid)
        .update(
          program: program,
        )
        .then(() => console.log("Program updated successfully!"))
        .catch((error: any) => console.error("Error updating program:", error));
     else 
      console.log("No changes to the program!");
    
  

  async getProgram() 
    firebaseApp.db
      .collection("users")
      .doc(this.user?.uid)
      .get()
      .then((doc) => 
        console.log("hello");
        if (doc.exists) 
          this.userProgram = doc.data()?.program;
          console.log("this.userProgram", this.userProgram);
         else 
          console.log("doc.data()", doc.data());
        
      );
  


const firebaseApp = new Firebase();
export default firebaseApp;

programContext.tsx

import React from "react";
import Program,  muscleGroup, DefaultProgram  from "../interfaces/program";

export interface ProgramContextInt 
  program: Program | undefined;
  days: Array<[string, muscleGroup]> | undefined;
  setProgram: (p: Program) => void;


export const DefaultProgramContext: ProgramContextInt = 
  program: undefined,
  days: undefined,
  setProgram: (p: Program): void => ,
;

const ProgramContext = React.createContext<ProgramContextInt>(
  DefaultProgramContext
);

export default ProgramContext;

useProgram.tsx


import React from "react";
import 
  ProgramContextInt,
  DefaultProgramContext,
 from "../../context/program";
import Program,  muscleGroup  from "../../interfaces/program";
import  useAuthState  from "react-firebase-hooks/auth";
import firebaseApp from "../../firebase";

export const useProgram = (): ProgramContextInt => 
  const [user] = useAuthState(firebaseApp.auth);

  const [program, setEditedProgram] = React.useState<Program | undefined>();
  const [days, setProgramDays] = React.useState<
    [string, muscleGroup][] | undefined
  >(program && Object.entries(program));

  const setProgram = React.useCallback(
    (program: Program): void => 
      firebaseApp.updateProgram(user, program);
      setEditedProgram(program);
      setProgramDays(Object.entries(program));
    ,
    [user]
  );

  return 
    program,
    days,
    setProgram,
  ;
;

【问题讨论】:

【参考方案1】:

我认为有两种方法可以处理:

    更新ProgramContext 以确保用户已登录 将App 或任何其他您需要确保用户已登录的入口点包装在单独的UserContextProvider

让我们谈谈后一种方法,我们可以将其包装在一个名为UserContext 的单独上下文中。 Firebase 为我们提供了一个名为 onAuthStateChanged 的侦听器,我们可以在上下文中使用它,如下所示:

import  createContext, useEffect, useState  from "react";
import fb from "services/firebase"; // you need to define this yourself. It's just getting the firebase instance. that's all
import fbHelper from "services/firebase/helpers"; // update path based on your project organization

type FirestoreDocSnapshot = firebase.default.firestore.DocumentSnapshot<firebase.default.firestore.DocumentData>;

const UserContext = createContext( user: null, loading: true );

const UserContextProvider = ( children ) => 
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const userContext =  user, loading ;

  const updateUser = (snapShot: FirestoreDocSnapshot) => 
    setUser(
      id: snapShot.id,
      ...snapShot.data,
    );
  ;

  const authStateListener = async (authUser: firebase.default.User) => 
    try 
      if (!authUser) 
        setUser(authUser);
        return;
      

      const fbUserRef = await fbHelper.findOrCreateFirestoreUser(authUser)

      if ("error" in fbUserRef) throw new Error(fbUserRef?.error);

      (fbUserRef as FirestoreUserRef).onSnapshot(updateUser)
     catch (error) 
      throw error;
     finally 
      setLoading(false);
    
  ;

  useEffect(() => 
    const unSubscribeAuthStateListener = fb.auth.onAuthStateChanged(authStateListener);

    return () => unSubscribeAuthStateListener();
  , [])

  return (
    <UserContext.Provider value=userContext>
      children
    </UserContext.Provider>
  )
;

export default UserContextProvider;

帮助器可以是这样的:

export type FirestoreUserRef = firebase.default.firestore.DocumentReference<firebase.default.firestore.DocumentData>

const findOrCreateFirestoreUser = async (authUser: firebase.default.User, additionalData = ): Promise<FirestoreUserRef |  error?: string > => 
  try 
    if (!authUser) return  error: 'authUser is missing!' ;

    const user = fb.firestore.doc(`users/$authUser.uid`); // update this logic according to your schema
    const snapShot = await user.get();

    if (snapShot.exists) return user;

    const  email  = authUser;

    await user.set(
      email,
      ...additionalData
    );

    return user;
   catch (error) 
    throw error;
  
;

然后将提供firestore 数据的其他上下文包装在此UserContextProvider 中。因此,每当您登录或注销时,都会调用这个特定的侦听器。

【讨论】:

以上是关于如何将默认反应上下文值设置为来自 Firestore 的数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 fetch() 函数的结果传递给 React 上下文值,而不是我为钩子设置的默认值?

如何设置单选按钮默认签入反应?

将本机设置状态反应到来自平面列表的文本值

Django ChoiceArrayField:如何设置默认值

如何将对象设置为表单值以与 Material UI 做出反应?

如何从一个反应组件到主 app.js 将布尔值设置为 false