ผู้ฟัง Firebase พร้อม React Hooks


27

ฉันกำลังพยายามหาวิธีใช้ผู้ฟัง Firebase เพื่อให้ข้อมูล firestore บนคลาวด์ได้รับการรีเฟรชด้วยการอัพเดต hooks ที่ตอบสนอง

ตอนแรกฉันทำสิ่งนี้โดยใช้องค์ประกอบคลาสที่มีฟังก์ชั่น 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);

คอมโพเนนต์นี้ถูกหุ้มใน wrapper 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 };

บริบทถูกตั้งค่าใน wrapper 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;

จากนั้นใน 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 กับองค์ประกอบฟังก์ชั่นได้หรือไม่?

ฉันเห็นแอพตัวอย่างนี้และโพสต์บล็อกนี้แล้ว

ทำตามคำแนะนำในบล็อกฉันสร้าง 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 ไม่ใช่ฟังก์ชัน

ฉันเห็นโพสต์นี้แล้วจัดการกับข้อผิดพลาดนั้นและได้ลองถอนการติดตั้งและติดตั้งเส้นด้าย มันไม่ต่างอะไรเลย

เมื่อฉันดูแอปสาธิตมันแนะนำบริบทที่ควรสร้างขึ้นโดยใช้วิธีการ 'อินเทอร์เฟซ' ฉันไม่เห็นว่าสิ่งนี้มาจากไหน - ฉันไม่สามารถหาข้อมูลอ้างอิงเพื่ออธิบายในเอกสารประกอบ

ฉันไม่สามารถเข้าใจถึงคำแนะนำอื่น ๆ นอกจากลองใช้สิ่งที่ฉันทำไว้เพื่อเสียบสิ่งนี้

ฉันได้เห็นโพสต์นี้ซึ่งพยายามฟังไฟร์โดยไม่ใช้ react-firebase-hooks คำตอบนั้นกลับไปที่การพยายามหาวิธีใช้เครื่องมือนี้

ฉันได้อ่านคำอธิบายที่ยอดเยี่ยมนี้ซึ่งจะอธิบายวิธีย้ายออกจาก HOCs ไปยังตะขอ ฉันติดอยู่กับวิธีรวมผู้ฟัง firebase เข้าด้วยกัน

ฉันได้เห็นโพสต์นี้ซึ่งให้ตัวอย่างที่เป็นประโยชน์สำหรับวิธีคิดเกี่ยวกับการทำเช่นนี้ ไม่แน่ใจว่าฉันควรพยายามทำสิ่งนี้ใน authListener componentDidMount - หรือในองค์ประกอบ Dashboard ที่พยายามจะใช้

ความพยายามถัดไป ฉันพบโพสต์นี้ซึ่งพยายามแก้ไขปัญหาเดียวกัน

เมื่อฉันพยายามที่จะใช้โซลูชันที่เสนอโดย Shubham Khatri ฉันตั้งค่าการกำหนดค่า firebase ดังนี้:

ผู้ให้บริการบริบทด้วย: import React, {useContext} จาก 'react'; นำเข้า Firebase จาก '../../firebase';

const FirebaseContext = React.createContext(); 

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

บริบทของ hook นั้นมี:

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;

และฉันพยายามใช้เป็นเส้นทางโดยไม่มีส่วนประกอบหรือ wrapper auth:

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

เมื่อฉันลองทำสิ่งนี้ฉันได้รับข้อผิดพลาดที่แจ้งว่า:

ข้อผิดพลาดในการนำเข้าที่พยายาม: 'FirebaseContext' ไม่ได้ถูกส่งออกจาก '../Firebase/ContextHookProvider'

ข้อความแสดงข้อผิดพลาดนั้นสมเหตุสมผลเพราะ ContextHookProvider ไม่ส่งออก FirebaseContext - ส่งออก FirebaseProvider - แต่ถ้าฉันไม่พยายามนำเข้าสิ่งนี้ใน Dashboard2 - ฉันไม่สามารถเข้าถึงได้ในฟังก์ชั่นที่พยายามจะใช้

ผลข้างเคียงหนึ่งของความพยายามนี้คือวิธีการสมัครใช้งานของฉันไม่ทำงานอีกต่อไป ตอนนี้จะสร้างข้อความแสดงข้อผิดพลาดที่แจ้งว่า:

TypeError: ไม่สามารถอ่านคุณสมบัติ 'doCreateUserWithEmailAndPassword' ของ null

ฉันจะแก้ปัญหานี้ในภายหลัง - แต่จะต้องมีวิธีที่จะคิดออกว่าจะใช้การตอบสนองกับ firebase ที่ไม่เกี่ยวข้องกับเดือนของวงนี้ผ่านทางหลายล้านที่ไม่ทำงานเพื่อให้ได้รับการตั้งค่าการตรวจสอบขั้นพื้นฐาน มีชุดเริ่มต้นสำหรับ firebase (firestore) ที่ใช้งานร่วมกับ hooks ได้หรือไม่?

ความพยายามครั้งต่อไป ฉันพยายามทำตามวิธีการในหลักสูตร udemyนี้- แต่ใช้เพื่อสร้างรูปแบบการป้อนข้อมูลเท่านั้น - ไม่มีผู้ฟังวางเส้นทางเพื่อปรับกับผู้ใช้ที่ได้รับการรับรองความถูกต้อง

ฉันพยายามติดตามวิธีการในบทช่วยสอน youtubeนี้- ซึ่งมีrepoนี้ใช้งานได้ มันแสดงให้เห็นถึงวิธีการใช้ hooks แต่ไม่ใช่วิธีการใช้บริบท

ความพยายามต่อไป ฉันพบrepo นี้ดูเหมือนจะมีวิธีคิดที่ดีในการใช้ hooks กับ firestore อย่างไรก็ตามฉันไม่สามารถเข้าใจรหัสได้

ฉันโคลนสิ่งนี้ - และพยายามเพิ่มไฟล์สาธารณะทั้งหมดและเมื่อฉันเรียกใช้ - ฉันไม่สามารถรับรหัสเพื่อใช้งานได้ ฉันไม่แน่ใจว่าสิ่งที่หายไปจากคำแนะนำสำหรับวิธีการทำงานนี้เพื่อดูว่ามีบทเรียนในรหัสที่สามารถช่วยแก้ปัญหานี้ได้หรือไม่

ความพยายามต่อไป

ฉันซื้อเทมเพลต divjoy ซึ่งโฆษณาว่าตั้งค่าไว้สำหรับ firebase (มันไม่ได้ติดตั้งสำหรับ firestore ในกรณีที่คนอื่นกำลังพิจารณาสิ่งนี้เป็นตัวเลือก)

เทมเพลตนั้นเสนอ wrapper การรับรองความถูกต้องที่เริ่มต้นการกำหนดค่าของแอป - แต่สำหรับวิธีการรับรองความถูกต้อง - ดังนั้นจึงต้องมีการปรับโครงสร้างใหม่เพื่อให้ผู้ให้บริการบริบทรายอื่นได้รับการยืนยัน เมื่อคุณยุ่งเหยิงในกระบวนการนั้นและใช้กระบวนการที่แสดงในโพสต์นี้สิ่งที่เหลืออยู่คือข้อผิดพลาดในการติดต่อกลับดังต่อไปนี้:

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 ฉันหุ้มแอปในผู้ให้บริการฐานข้อมูล

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 และสร้างข้อผิดพลาดเดียวกัน

เมื่อฉันลองทำตามคำแนะนำของ Tristan ฉันจะลบสิ่งเหล่านั้นทั้งหมดและลองและกำหนดวิธีการยกเลิกการสมัครของฉันเป็นวิธีที่ไม่แสดง (ฉันไม่รู้ว่าทำไมเขาไม่ใช้ภาษา firebase - แต่ถ้าวิธีของเขาใช้งานได้ฉันจะพยายามให้มากขึ้น เพื่อหาสาเหตุ) เมื่อฉันพยายามใช้วิธีแก้ปัญหาข้อความแสดงข้อผิดพลาดแจ้งว่า:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ ค่าเริ่มต้น (... ) ไม่ใช่ฟังก์ชัน

คำตอบสำหรับโพสต์นี้แนะนำให้ลบ () จากหลังจากรับรองความถูกต้อง เมื่อฉันลองฉันจะได้รับข้อผิดพลาดที่แจ้งว่า:

TypeError: ไม่สามารถอ่านคุณสมบัติ 'onAuthStateChanged' ของที่ไม่ได้กำหนด

อย่างไรก็ตามโพสต์นี้แนะนำปัญหาเกี่ยวกับวิธีการนำเข้าฐานข้อมูลในไฟล์รับรองความถูกต้อง

ฉันได้นำเข้าเป็น: import Firebase จาก "../firebase";

Firebase เป็นชื่อของคลาส

วิดีโอ Tristan แนะนำเป็นพื้นหลังที่เป็นประโยชน์ แต่ตอนนี้ฉันอยู่ตอนที่ 9 และยังไม่พบส่วนที่ควรจะช่วยแก้ปัญหานี้ ไม่มีใครรู้ว่าจะหาได้ที่ไหน?

NEXT ATTEMPT ถัดไป - และพยายามแก้ไขปัญหาบริบทเท่านั้น - ฉันได้นำเข้าทั้ง createContext และ useContext และพยายามใช้ดังที่แสดงในเอกสารนี้

ฉันไม่สามารถรับข้อผิดพลาดที่แจ้งว่า:

ข้อผิดพลาด: การโทรขอไม่ถูกต้อง สามารถเรียกใช้ Hooks ได้เฉพาะภายในร่างกายของส่วนประกอบฟังก์ชันเท่านั้น สิ่งนี้อาจเกิดขึ้นได้จากสาเหตุใดสาเหตุหนึ่งต่อไปนี้: ...

ฉันได้รับคำแนะนำในลิงค์นี้แล้วเพื่อลองและแก้ไขปัญหานี้และไม่สามารถเข้าใจได้ ฉันไม่มีปัญหาใด ๆ ที่แสดงในคู่มือการแก้ไขปัญหานี้

ปัจจุบัน - คำสั่งบริบทมีลักษณะดังนี้:

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นี้เพื่อลองและวิเคราะห์องค์ประกอบบริบทและ hooks ของปัญหานี้ - หลังจากดูแล้ว - สิ่งเดียวที่โซลูชันเสนอโดย Tristan ด้านล่างคือวิธี createContext ไม่ได้เรียกอย่างถูกต้องในโพสต์ของเขา มันต้องเป็น "React.createContext" แต่ก็ยังไม่ใกล้ที่จะแก้ปัญหา

ฉันยังคงติดอยู่

ทุกคนสามารถเห็นสิ่งที่ผิดพลาดที่นี่?


มันไม่ได้กำหนดเพราะคุณไม่ได้นำเข้า
Josh Pittman

3
คุณจะทำเพียงแค่ต้องเพิ่มexportไปexport const FirebaseContext?
Federkun

สวัสดีเมลขอโทษที่ตอบช้าฉันมีงานสองสัปดาห์ที่บ้าคลั่งดังนั้นการดูคอมพิวเตอร์ในตอนเย็นก็ไม่เป็นไปตามคำถาม! ฉันได้อัปเดตคำตอบสำหรับคุณแล้วเพื่อมอบโซลูชันที่สะอาดและเรียบง่ายซึ่งคุณสามารถตรวจสอบได้
Tristan Trainer

ขอบคุณมาก - ฉันจะดูทันที
Mel

สวัสดี Mel เพิ่งปรับปรุงเพิ่มเติมพร้อมกับการใช้งานการอัปเดตแบบเรียลไทม์จาก firestore อย่างถูกต้อง (สามารถลบส่วน onSnapshot เพื่อให้ไม่ใช่แบบเรียลไทม์) หากนี่เป็นวิธีการแก้ปัญหาฉันขอแนะนำให้อัพเดทคำถามของคุณเพื่อให้เป็น สั้นลงและกระชับมากขึ้นเพื่อให้คนอื่นดูว่าอาจพบวิธีแก้ปัญหาด้วยขอบคุณอีกครั้งขออภัยในความช้าของการตอบสนอง
Tristan Trainer

คำตอบ:


12

การแก้ไขที่สำคัญ : ใช้เวลาสักครู่เพื่อค้นหาสิ่งนี้อีกเล็กน้อยนี่คือสิ่งที่ฉันได้มาด้วยเป็นวิธีที่สะอาดกว่าใครบางคนอาจไม่เห็นด้วยกับฉันเกี่ยวกับเรื่องนี้เป็นวิธีที่ดีในการเข้าถึงสิ่งนี้

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 เป็นโมฆะก็จะไม่ผ่านการตรวจสอบสิทธิ์หากผู้ใช้มีค่า

firebaseConfigสามารถพบได้บน firebase Console => การตั้งค่าโครงการ => แอพ => ปุ่มตัวเลือกการกำหนดค่า

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

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

การใช้ประโยชน์นี้ขอผลเป็นหลักในการติดตามการตรวจสอบการเปลี่ยนแปลงของผู้ใช้ เราเพิ่มผู้ฟังไปยังเหตุการณ์ onAuthStateChanged ของ firebase.auth () ที่อัพเดตค่าของ authUser วิธีการนี้จะส่งกลับโทรกลับสำหรับการยกเลิกการสมัครฟังนี้ซึ่งเราสามารถใช้ในการทำความสะอาดผู้ฟังเมื่อใช้ตะขอ FireFire ฐานจะมีการรีเฟรช

นี่เป็นเบ็ดเดียวที่เราต้องการสำหรับการรับรองความถูกต้องของ firebase (hooks อื่น ๆ สามารถทำเพื่อ 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>
}

นี่คือการใช้งานพื้นฐานของAppองค์ประกอบของcreate-react-app

useFirestore ตะขอเกี่ยวฐานข้อมูล

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. การบันทึกเอกสารที่ไม่เปลี่ยนแปลงไม่ทำให้เกิด snapshot ใหม่ดังนั้น "oversaving" จะไม่ทำให้เกิดการแสดงซ้ำ
  2. ในการเรียกใช้ getDocument การเรียกกลับ onUpdate จะถูกเรียกทันทีด้วย "snapshot" เริ่มต้นดังนั้นคุณไม่จำเป็นต้องใช้รหัสเพิ่มเติมสำหรับรับสถานะเริ่มต้นของเอกสาร

การแก้ไขได้ลบคำตอบเก่า ๆ จำนวนมากออกไป


1
ขอบคุณสำหรับสิ่งนี้. ฉันไม่เห็นวิธีที่คุณตั้งค่านี้ ด้วยผู้ให้บริการฉันได้รับข้อผิดพลาดที่ระบุว่า createContext ไม่ได้กำหนดไว้ นั่นเป็นเพราะไม่มีผู้บริโภคจนถึงตอนนี้ คุณเอาของคุณไปที่ไหน
Mel

สวัสดีขอโทษ createContext เป็นส่วนหนึ่งของการตอบสนองดังนั้นนำเข้าที่ด้านบนเป็น {createContext} จาก 'ปฏิกิริยา' ฉันรู้ว่าฉันลืมที่จะแสดงว่าผู้ให้บริการ Firebase ไปที่ใดฉันจะแก้ไขคำตอบ
Tristan Trainer

ฉันนำเข้ามาจากผู้ให้บริการ แต่มันไม่ได้กำหนดไว้ ฉันคิดว่าเป็นเพราะไม่มีผู้บริโภคสำหรับมัน
เมล

1
ผู้บริโภคคือตะขอ useContext () แต่เมื่อดูคำถามของคุณอีกครั้งดูเหมือนว่าคุณไม่ได้ส่งออก FirebaseContext จากไฟล์ - นั่นคือสาเหตุที่ไม่สามารถค้นหาบริบทได้ :)
Tristan Trainer

1
สวัสดี @Mel ขอบคุณมากที่ฉันหวังว่ามันจะเป็นประโยชน์ในตอนท้าย Hooks และ Firebase มีส่วนเกี่ยวข้องค่อนข้างมากและใช้เวลานานกว่าจะได้รับรอบ ๆ ตัวฉันและฉันอาจไม่พบโซลูชันที่ดีที่สุดในตอนนี้ฉันอาจสร้างแบบฝึกหัดเกี่ยวกับวิธีการของฉันในบางครั้งเพราะอธิบายได้ง่ายขึ้น มัน.
Tristan Trainer

4

Firebase ไม่ได้ถูกกำหนดเนื่องจากคุณไม่ได้นำเข้า ครั้งแรกต้องที่จะได้รับfirebase.firestore()ตามที่แสดงในตัวอย่างบนเอกสารที่คุณเชื่อมโยงกับhttps://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore จากนั้นคุณต้องนำเข้า firebase จริง ๆ ในไฟล์ดังimport * as firebase from 'firebase';ที่แสดงไว้ในแพ็คเกจ firebase readme https://www.npmjs.com/package/firebase


1
ฉันกำลังนำเข้ามันใน index.js
เมล

1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
เมล

1
นั่นเป็นเหตุผลที่วิธีการทำงานกับ componentDidMount
เมล

1
withAuth HOC นั้นรวมอยู่ใน FireFire
เมล

3
แต่ข้อผิดพลาดของคุณบอกว่ามันไม่ได้กำหนด ฉันกำลังช่วยคุณกำหนดและคุณไม่ได้บอกฉันว่าวิธีแก้ปัญหาใช้งานได้หรือเกิดข้อผิดพลาดอะไรขึ้น คุณทำให้มันยากมากที่จะช่วยคุณเมล จุดของฉันคือคุณกำลังนำเข้าในไฟล์อื่นนอกเหนือจากไฟล์องค์ประกอบ dashboard2 ซึ่งเป็นที่ที่คุณอ้างอิงซึ่งเป็นสาเหตุของข้อผิดพลาด การนำเข้าบางอย่างในดัชนีไม่ช่วยให้งานสร้างของคุณเข้าใจสิ่งที่อยู่ในไฟล์ที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
Josh Pittman

2

แก้ไข (3 มีนาคม 2563):

เริ่มจากศูนย์เลย

  1. ฉันได้สร้างโครงการใหม่:

    เส้นด้ายสร้างปฎิกิริยาตอบโต้แอพ firebase-hook-issue

  2. ฉันได้ลบไฟล์แอป * ทั้งหมด 3 ไฟล์ที่สร้างโดยค่าเริ่มต้นลบการนำเข้าออกจาก index.js และลบผู้ปฏิบัติงานบริการเพื่อให้มี 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 ที่ว่างเปล่าแล้วและฉันได้เลือกฐานข้อมูลและ Firestore ซึ่งท้ายที่สุดก็คือการสร้างไฟล์ถัดไป:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. ฉันได้เพิ่มการนำเข้า libs 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"));
  1. เรามาตรวจสอบว่าเราสามารถอ่านอะไรจาก Firestore ในการตรวจสอบการอ่านก่อนอื่นฉันไปที่โครงการของฉันใน Firebase Console เปิดฐานข้อมูล Cloud Firestore ของฉันและเพิ่มคอลเล็กชันใหม่ที่เรียกว่าเคาน์เตอร์พร้อมเอกสารง่าย ๆที่มีหนึ่งฟิลด์ที่เรียกว่ามูลค่าประเภทตัวเลขและค่า 0 ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

  2. จากนั้นฉันได้อัปเดตคลาสแอปเพื่อใช้ FirebaseContext ที่เราสร้างขึ้นให้ใช้ hook State สำหรับ counter hook ที่เรียบง่ายของเราและใช้ useEffect hook เพื่ออ่านค่าจาก 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"));

หมายเหตุ: เพื่อให้คำตอบสั้นที่สุดเท่าที่จะทำได้ฉันแน่ใจว่าคุณไม่จำเป็นต้องรับรองความถูกต้องของ firebase โดยการตั้งค่าการเข้าถึง firestore ให้อยู่ในโหมดทดสอบ (ไฟล์ 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-Skeleton:

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

ส่วนใหญ่จะเป็นไปตามบทความ:

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

แต่ค่อนข้างเขียนใหม่เพื่อใช้ hooks ฉันใช้มันอย่างน้อยสองโครงการของฉัน

การใช้งานทั่วไปจากโครงการสัตว์เลี้ยงปัจจุบันของฉัน:

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 () hook ซึ่งให้ผู้ใช้ที่ผ่านการตรวจสอบแล้วในปัจจุบัน - FirebaseContext ที่ฉันใช้ผ่านทางuseContext hook

const firebase = useContext(FirebaseContext);

เมื่อคุณมีบริบทเพื่อ firebase มันขึ้นอยู่กับคุณที่จะใช้วัตถุฐานไฟเพื่อความชอบของคุณ บางครั้งฉันเขียนฟังก์ชั่นที่มีประโยชน์บางครั้งมันง่ายกว่าที่จะเพียงแค่ตั้งค่าฟังในการใช้ประโยชน์ผลเบ็ดฉันสร้างขึ้นสำหรับองค์ประกอบปัจจุบันของฉัน

หนึ่งในส่วนที่ดีที่สุดของบทความนั้นคือการสร้างwithAuthorization HOC ซึ่งช่วยให้คุณสามารถระบุสิ่งที่จำเป็นต้องมีสำหรับการเข้าถึงหน้าในองค์ประกอบนั้น:

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

หรืออาจตั้งค่าเงื่อนไขเหล่านั้นในการติดตั้งเราเตอร์ของคุณ

หวังว่าการดูรายงานซื้อคืนและบทความจะช่วยให้คุณมีความคิดที่ดีเป็นพิเศษเพื่อเพิ่มคำตอบให้กับคำถามของคุณ


ฉันซื้อหนังสือของเขาและทำตามวิธีการของเขา ฉันพบว่าวิธีการเงื่อนไขไม่ได้ผลจริงเมื่อนำไปใช้และการรับรองความถูกต้องที่กำหนดไว้ในหนังสือเล่มนั้นไม่สามารถรักษาสถานะผ่านการอัปเดตองค์ประกอบ ฉันไม่พบวิธีใช้สิ่งที่กำหนดไว้ในหนังสือเล่มนั้น ขอบคุณสำหรับการแบ่งปันความคิดของคุณ
Mel

ฉันไม่แน่ใจว่าคุณหมายถึงอะไร คุณลองแอปโครงกระดูกของฉันกับโปรเจค Firebase ของคุณหรือไม่? เงื่อนไขทั้งหมดใช้ได้เท่าที่ฉันจะบอกเพราะฉันใช้มันในโครงการอย่างน้อย 3 โครงการ
fxdxpz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.