【问题标题】:How to save image uploaded from firebase storage to currentUser in firestore database如何将从firebase存储上传的图像保存到firestore数据库中的currentUser
【发布时间】:2022-01-06 15:29:48
【问题描述】:

我不确定如何获取上传到 firebase 存储的图片 url 并将其保存到 firestore 数据库。目前我有一个配置文件组件,用于存储用户将照片上传到 Firebase 存储的逻辑。我有一个监听用户更改的 UserContext 文件。以及创建用户的 Firebase.utils。

Firebase.utils 文件:

import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/auth';
import "firebase/compat/storage";

const config = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
};

firebase.initializeApp(config);

export const createUserProfileDocument = async (userAuth, additionalData) => {
    // If user is not signed in do nothing
    if (!userAuth) return;

    const userRef = firestore.doc(`users/${userAuth.uid}`)

    const snapShot = await userRef.get()

    if (!snapShot.exists) {
        // Added photoURL @@@
        const { displayName, email, photoURL } = userAuth;
        const createdAt = new Date();

        try {
            await userRef.set({
                displayName,
                email,
                createdAt,
                photoURL,
                ...additionalData
            })
        } catch (error) {
            console.log('error creating user', error.message)
        }
    }

    return userRef;
}

export const auth = firebase.auth();
export const firestore = firebase.firestore();
const storage = firebase.storage();; 

export { storage, firebase as default };

UserContext 文件:
import React, { useContext, useState, useEffect } from 'react';
import { auth, createUserProfileDocument } from "../Firebase/Firebase.utils";

const UserContext = React.createContext(null);
const UserUpdateContext = React.createContext();
const UserUpdateNameContext = React.createContext();
const UserUpdateEmailContext = React.createContext();

export const useUserContext = () => {
    // useContext hook 
    return useContext(UserContext);
}

export const useUserContextUpdate = () => {
    // useContext hook - toggleUser signout function
    return useContext(UserUpdateContext)
}

export const useUserNameUpdate = () => {
    // useContext hook - update user displayName
    return useContext(UserUpdateNameContext)
}

export const useUserEmailUpdate = () => {
    // useContext hook - update user email
    return useContext(UserUpdateEmailContext)
}

export const UserContextProvider = ({ children }) => {
    const [currentUser, setUser] = useState(null);
    let unsubscribeFromAuth = null;
    console.log(currentUser)

    useEffect(() => {
        unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
            if (userAuth) {
                const userRef = await createUserProfileDocument(userAuth);

                userRef.onSnapshot(snapShot => {
                    setUser({
                        id: snapShot.id,
                        ...snapShot.data()
                    });
                });
            } else {
                setUser(null)
                // setUser({ currentUser: userAuth }) OBJECTS ARE TRUTHY 
            }
        });

        return () => {
            unsubscribeFromAuth();
        };
    }, [])
    console.log(unsubscribeFromAuth)

    const toggleUser = () => {
        auth.signOut()
            .then(() => {
                setUser(null)
            })
            .catch(e => console.log('There was a error:'(e)))
    }
    // console.log(currentUser)

    // Get current window width
    const useWindowWidth = () => {
        const [width, setWidth] = useState(window.innerWidth)
        useEffect(() => {
            const handleResize = () => setWidth(window.innerWidth)
            window.addEventListener('resize', handleResize)
            return () => {
                window.removeEventListener('resize', handleResize)
            }
        })
        return width
    }
    const width = useWindowWidth();

    // Slice off end of displayName if reaches a certain length
    const sliceDisplayName = (currentUser) => {
        if (currentUser) {
            const displayName = currentUser.displayName;
            return (
                width >= 1441 ? displayName.substring(0, 16) + '...'
                    : width <= 1440 && width >= 769 ? displayName.substring(0, 14) + '...'
                        : width <= 768 ? displayName.substring(0, 7) + '...'
                            : displayName
            )
        } else (console.log("No user found :("))
    }
    // console.log(sliceDisplayName(currentUser))

    // Slice off end of email if reaches a certain length
    const sliceEmail = (currentUser) => {
        if (currentUser) {
            const email = currentUser.email;
            return (
                width >= 1441 ? email.substring(0, 16) + '...'
                    : width <= 1440 && width >= 769 ? email.substring(0, 14) + '...'
                        : width <= 768 ? email.substring(0, 7) + '...'
                            : email
            )
        } else (console.log("No user found :("))
    }
    // console.log(sliceEmail(currentUser))

    return (
        <UserContext.Provider value={currentUser} >
            <UserUpdateContext.Provider value={toggleUser} >
                <UserUpdateNameContext.Provider value={sliceDisplayName} >
                    <UserUpdateEmailContext.Provider value={sliceEmail} >
                        {children}
                    </UserUpdateEmailContext.Provider >
                </UserUpdateNameContext.Provider >
            </UserUpdateContext.Provider >
        </UserContext.Provider >
    )
};

配置文件组件:
import React, { useState } from 'react';
import ReactTooltip from 'react-tooltip';
import { useUserContext, useUserContextUpdate, useUserNameUpdate } from '../../Utilities/Context/UserContext';
import { storage } from "../../Utilities/Firebase/Firebase.utils";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; 
import ActivityFeed from '../../../src/components/ActivityFeed/ActivityFeed';
import Post from '../../../src/components/Post/Post';

import './Profile.css';

const Profile = ({ imageDate }) => {
    const currentUser = useUserContext(); // Current user
    const sliceDisplayName = useUserNameUpdate(); // Window width < (width) ? update displayName length
    const [image, setImage] = useState("");
    const [url, setUrl] = useState("");

    // Listen for state changes, errors, and completion of the upload.
    const handleUpload = () => {
        const uploadTask = storage.ref(`images/${image.name}`).put(image);
        uploadTask.on('state_changed',
            (snapshot) => { console.log(snapshot) },
            (error) => {
                switch (error.code) {
                    case 'storage/unauthorized':
                        break;
                    case 'storage/canceled':
                        break;
                    case 'storage/unknown':
                        break;
                }
            },
            (e) => {
                storage
                    .ref("images")
                    .child(image.name)
                    .getDownloadURL(uploadTask.snapshot.ref)
                    .then(url => {
                        setUrl(url);
                        console.log('File available at', url);
                    })
            }
        );
    }
    console.log("image: ", image);
    console.log(image.lastModifiedDate) // Image date uploaded. image.lastmodifiedDate === undefined ? 
    console.log(url)

    const handleUploadChange = e => { // Maybe inside this function i can do the logic for recent activity and 0 photos +1
        if (e.target.files[0]) {
            setImage(e.target.files[0]);
        }
    };

    return (
        <div className="container-flex">
            <div className="top-content-container w-100">
                <div className="bot-content-wrapper px-1 py-2 mx-lg-auto d-flex flex-column flex-lg-row">
                    <div className="w-spacer profile-image-wrapper position-relative">
                        {
                            currentUser ?
                                <input
                                    type="file"
                                    for="Upload Image"
                                    accept="image/*"
                                    name="image"
                                    id="file"
                                    onChange={handleUploadChange}
                                    onClick={handleUploadChange}
                                    style={{ display: "none" }}
                                />
                                : ''
                        
                        }
                        <label for="file">  
                            {
                                url.length <= 0 ?
                                    <img
                                        id='myimg'
                                        className="profile-image-default profile-image d-block"
                                        alt=""
                                    />
                                : currentUser ?
                                    <>
                                        <img
                                            id='myimg'
                                            className="profile-image d-block"
                                            src={url}
                                            alt=""
                                            data-tip="Click me to update profile picture!" />                                    
                                        <ReactTooltip place="top" type="dark" effect="float" />
                                    </>
                                :
                                    <img
                                        id='myimg'
                                        className="profile-image-default profile-image d-block"
                                        alt=""
                                    />
                            }
                        </label>
                    </div>
                    <div className="d-flex flex-column flex-lg-row align-items-lg-center w-lg-75 m-l-4">
                        <div className="d-flex flex-column flex-lg-row ml-auto pr-1 m-r-md-vw">
                            <div className="m-r-md">
                                <div className="d-flex flex-column w-100 m-r-6">
                                    <div>
                                        {
                                            currentUser ?
                                                <h2
                                                    data-tip={currentUser.displayName}>
                                                    {sliceDisplayName(currentUser)}
                                                    <span><ReactTooltip place="top" type="dark" effect="float" /></span>
                                                </h2>
                                                :
                                                <h2>No User</h2>
                                        }
                                    </div>
                                    <div className="d-flex flex-column flex-lg-row">
                                        <div className="">
                                            <i className="bi bi-people"></i>
                                            <span className="banner-list-font mx-1">0 friends</span>
                                        </div>
                                        <div className="mx-lg-2">
                                            <i className="bi bi-star"></i>
                                            <span className="banner-list-font mx-1">0 reviews</span>
                                        </div>
                                        <div className="">
                                            <i className="bi bi-camera"></i>
                                            <span className="banner-list-font mx-1">0 photos</span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <hr className=" d-lg-none" style={{ color: '#0a0a0a' }}></hr>
                            <div className="ml-3">
                                <div className="update-profile-wrapper grey-line-break d-flex flex-column m-l">
                                    <div className="">
                                        {
                                            image.name !== undefined ?
                                                <button
                                                    className="banner-list-font"
                                                    onClick={handleUpload}
                                                    //  onClick={() => {
                                                    //      handleUpload();
                                                    //      forceUpdate();
                                                    //  }}
                                                >Add Profile Photo
                                                </button>
                                                : ''
                                        }
                                    </div>
                                    <div className="">
                                        <a className="banner-list-font" href='#'>Update Your Profile</a>
                                    </div>
                                    <div className="">
                                        <a className="banner-list-font" href='#'>Find Friends</a>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div className="bot-content-container px-1 py-4 custom-padding">
                <div className="bot-content-wrapper mx-lg-auto d-flex flex-column flex-lg-row">
                    <div className="sidebar d-flex flex-column mx-auto mx-lg-0 mt-lg-5 py-lg-2 px-2">
                        {
                            currentUser ?
                                <h4 className="mb-3">{currentUser.displayName}</h4>
                                :
                                <h4 className="mb-3">No User</h4>
                        }
                        <ul className="p-0">
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-person-badge"></i>
                                    <h5 className="sidebar-list-font">Overview</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-person-plus"></i>
                                    <h5 className="sidebar-list-font">Friends</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-award mx-1"></i>
                                    <h5 className="sidebar-list-font">Reviews</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-lightbulb"></i>
                                    <h5 className="sidebar-list-font">Tips</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-bookmark-star"></i>
                                    <h5 className="sidebar-list-font">Bookmarks</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-bookmarks"></i>
                                    <h5 className="sidebar-list-font">Collections</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-calendar-check"></i>
                                    <h5 className="sidebar-list-font">Events</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                            <a className="cursor-pointer text-decoration-none">
                                <li className="d-flex flex-row sidebar-item sidebar-list-font">
                                    <i className="mx-2 bi bi-clock-history"></i>
                                    <h5 className="sidebar-list-font">Order History</h5>
                                </li>
                                <hr style={{ color: '#0a0a0a' }}></hr>
                            </a>
                        </ul>
                    </div>
                    <div className="d-flex flex-column flex-lg-row w-100-md w-75-lg p-3 p-lg-0 m-l-4 pt-lg-3 pt-xl-4">
                        <div className="activity m-l-3">
                            <h3 className="heading-red">Notifications</h3>
                            <p className="font-14">No new friend requests or compliments at this time.</p>
                            <hr className="d-none d-lg-block" style={{ color: '#0a0a0a' }}></hr>
                            <h3 className="heading-red">Recent Activity</h3>
                            {<ActivityFeed />}
                            {<Post />}
                        </div>
                        <hr className="d-lg-none" style={{ color: '#0a0a0a' }}></hr>
                        <div className="grey-line-break ml-3">
                            <h3 className="heading-red mb-1 break-word">About
                                {
                                    currentUser ?
                                        <h3
                                            data-tip={currentUser.displayName}
                                            className="heading-red mb-1">
                                            {sliceDisplayName(currentUser)}
                                            <span><ReactTooltip place="top" type="dark" effect="float" /></span>
                                        </h3>
                                        :
                                        <h3 className="heading-red mb-1">No User</h3>
                                }
                            </h3>
                            <h5 className="about-subHeading mt-2">Yelping Since</h5>
                            <p className="font-14">Some month</p>
                            <h5 className="about-subHeading mt-2">Things I Love</h5>
                            <p className="font-14">You haven't said yet...</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default Profile;

**编辑**
更新了上传任务
import { createUserProfileDocument } from "../../Utilities/Firebase/Firebase.utils";

    () => {
        storage
            .ref('images/')
            .child(image.name)
            .getDownloadURL(uploadTask.snapshot.ref)
            .then(async (url, firestore, userAuth) => {
                setUrl(url);
                const userRef = firestore.doc(`users/${userAuth.uid}`)
                await userRef.update({
                    photoURL: url
                });
                console.log('File available at', url);
            })
    }

更新后出错:
未处理的拒绝(TypeError):无法读取未定义的属性(读取“文档”)

**编辑更新 #2**
在 Frank van Puffelen 的帮助下,我似乎设法从存储中获取上传的照片进入 firestore 数据库。我可以在 photoURL 中当前用户的 console.log 中看到它,它也显示在 firebase db 用户集合中。但是,如果我刷新或转到另一个页面,图像就会消失,即使刷新后它仍然在 currentUser console.log 中显示 url。为什么会这样?一定是因为 setUrl(url) 在每次渲染时都会将 url 图像的状态重新初始化为空。我应该很好地放弃这一切并直接从 currentUser 调用图像 url,例如: currentUser.photoURL

更新代码:
import { storage, firestore } from "../../Utilities/Firebase/Firebase.utils";

    () => {
        storage
            .ref('images/')
            .child(image.name)
            .getDownloadURL(uploadTask.snapshot.ref)
            .then(async (url) => {
                setUrl(url);
                const userRef = firestore.doc(`users/${currentUser.id}`)
                await userRef.update({
                    photoURL: url
                });
                console.log('File available at', url);
            })
    }

【问题讨论】:

    标签: reactjs firebase-realtime-database react-hooks firebase-authentication firebase-storage


    【解决方案1】:

    据我所知,您上传图片并在此 sn-p 中获取其下载 URL:

    storage
        .ref("images")
        .child(image.name)
        .getDownloadURL(uploadTask.snapshot.ref)
        .then(url => {
            setUrl(url);
            console.log('File available at', url);
        })
    

    要将新 URL 写入 Firestore 中的用户配置文件文档,请将其添加到 then() 回调:

    const userRef = firestore.doc(`users/${userAuth.uid}`)
    await userRef.update({
        photoURL: url
    });
    

    【讨论】:

    • 感谢您回答弗兰克,我更新了问题以反映这一点...我收到了错误回复(请参阅更新后的问题)。
    • 这不行:.then(async (url, firestore, userAuth) =&gt; {。您不能只在此处键入变量,因为 getDownloadURL 没有传递这些值。您需要找到另一种方法来访问 firestore 和其中的 UID:const firestore = firebase.firestore(); 和 const uid = firebase.auth().currentUser.uid;。
    • 所以为了清楚起见,我现在可以放弃 [url, setUrl] = useState("") 并使用 currentUser.photoURL 调用用户图像。 url,setUrl 导致图像在每次重新渲染时消失,因为状态正在初始化为空,对吗?
    • 如果你想更新本地 React 状态,你需要useState。如果您有用户的 Firestore 文档的实时侦听器,您还可以从该侦听器更新 UI,但我没有立即在您的代码中看到这一点。 --- 请注意,您的原始问题(“如何将从 firebase 存储上传的图像保存到 firestore 数据库中的 currentUser”)似乎已解决,因此我建议您转到一个新问题并为此设置一个新的单独的 MCVE .
    • 我相信 useEffect 钩子监听 auth.onAuthStateChanged(async userAuth => { in UserContext file ?
    猜你喜欢
    • 2021-03-14
    • 2020-12-18
    • 1970-01-01
    • 2019-07-24
    • 2017-10-19
    • 2021-10-14
    • 1970-01-01
    • 2020-09-24
    • 2018-06-04
    相关资源
    最近更新 更多