From 2a8cceb15164353bfd316ad978abe0bcbe017689 Mon Sep 17 00:00:00 2001 From: "Eray Erdin (&mut self)" Date: Thu, 1 Feb 2024 17:38:21 +0300 Subject: [PATCH] Add `FirebaseStorageProvider` (#48) * init FirebaseStorageProvider * test and impl storage instance should be accessible * add storage to FirebaseSuiteProvider * write docs for FirebaseStorageProvider * write docs for FirebaseSuiteProvider.storage prop --- docs/contexts-and-providers.md | 27 +++++++++++++++++++ .../FirebaseStorageProvider.test.tsx | 24 +++++++++++++++++ src/providers/FirebaseStorageProvider.tsx | 27 +++++++++++++++++++ src/providers/FirebaseSuiteProvider.test.tsx | 9 ++++++- src/providers/FirebaseSuiteProvider.tsx | 15 ++++++++++- src/providers/index.ts | 2 ++ 6 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/providers/FirebaseStorageProvider.test.tsx create mode 100644 src/providers/FirebaseStorageProvider.tsx diff --git a/docs/contexts-and-providers.md b/docs/contexts-and-providers.md index 052f327..876c2dd 100644 --- a/docs/contexts-and-providers.md +++ b/docs/contexts-and-providers.md @@ -119,6 +119,31 @@ const functions = useContext(FirebaseFunctionsContext); !!! tip The type you get from `useContext` is `Functions | undefined`. You can simply use `functions!` (non-null assertion operator) as you guarantee it to be defined when you use `FirebaseFunctionsProvider`. +### Setting Up for Firebase Storage + +Head to your entry point and wrap your app with `FirebaseStorageProvider`: + +```typescript +ReactDOM.createRoot(document.getElementById('root')!).render( + + + {/** the rest of your app */} + + +) +``` + +`FirebaseStorageProvider` only requires one parameter with signature `storage: FirebaseStorage`. + +Now, you can get the instance of your `FirebaseStorage` anywhere in the component tree by simply doing: + +```typescript +const storage = useContext(FirebaseStorageContext); +``` + +!!! tip + The type you get from `useContext` is `FirebaseStorage | undefined`. You can simply use `functions!` (non-null assertion operator) as you guarantee it to be defined when you use `FirebaseStorageProvider`. + ### Setting Up for Multiple Instances Sometimes, wrapping your entry point with multiple providers might look ugly. In that case, you can use `FirebaseSuiteProvider`, which lets you selectively choose what types of Firebase services should be injected into your global app context. @@ -133,6 +158,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render( firestore={firestore} {/** optional */} auth={auth} {/** optional */} functions={functions} {/** optional */} + storage={storage} {/** optional */} > {/** the rest of your app */} @@ -149,6 +175,7 @@ const app = useContext(FirebaseAppContext); const firestore = useContext(FirestoreContext); const auth = useContext(FirebaseAuthContext); const functions = useContext(FirebaseFunctionsContext); +const storage = useContext(FirebaseStorageContext); ``` !!! warning diff --git a/src/providers/FirebaseStorageProvider.test.tsx b/src/providers/FirebaseStorageProvider.test.tsx new file mode 100644 index 0000000..1eb6ec1 --- /dev/null +++ b/src/providers/FirebaseStorageProvider.test.tsx @@ -0,0 +1,24 @@ +// Copyright (c) 2024 Eray Erdin +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +import { render, screen } from "@testing-library/react"; +import { useContext } from "react"; +import { FirebaseStorageContext, FirebaseStorageProvider } from "."; +import { storage } from "../firebase"; + +const SampleComponent = () => { + const storage = useContext(FirebaseStorageContext); + return
{storage?.app.name}
; +}; + +it("storage instance should be accessible", async () => { + render( + + + , + ); + + expect(screen.getByText("[DEFAULT]")).not.toBeUndefined(); +}); diff --git a/src/providers/FirebaseStorageProvider.tsx b/src/providers/FirebaseStorageProvider.tsx new file mode 100644 index 0000000..36afec0 --- /dev/null +++ b/src/providers/FirebaseStorageProvider.tsx @@ -0,0 +1,27 @@ +// Copyright (c) 2024 Eray Erdin +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +import { FirebaseStorage } from "firebase/storage"; +import { createContext } from "react"; +import { NodeComponent } from "../types"; + +export const FirebaseStorageContext = createContext< + FirebaseStorage | undefined +>(undefined); + +type FirebaseStorageProviderProps = { + storage: FirebaseStorage; +} & NodeComponent; + +export const FirebaseStorageProvider = ({ + storage, + children, +}: FirebaseStorageProviderProps) => { + return ( + + {children} + + ); +}; diff --git a/src/providers/FirebaseSuiteProvider.test.tsx b/src/providers/FirebaseSuiteProvider.test.tsx index 5a23c47..6f0c629 100644 --- a/src/providers/FirebaseSuiteProvider.test.tsx +++ b/src/providers/FirebaseSuiteProvider.test.tsx @@ -9,8 +9,9 @@ import { FirebaseAppContext, FirebaseAuthContext, FirebaseFunctionsContext, + FirebaseStorageContext, } from "."; -import app, { auth, firestore, functions } from "../firebase"; +import app, { auth, firestore, functions, storage } from "../firebase"; import { FirebaseSuiteProvider } from "./FirebaseSuiteProvider"; import { FirestoreContext } from "./FirestoreProvider"; @@ -19,6 +20,7 @@ const SampleComponent = () => { const firestore = useContext(FirestoreContext); const auth = useContext(FirebaseAuthContext); const functions = useContext(FirebaseFunctionsContext); + const storage = useContext(FirebaseStorageContext); return ( <> @@ -26,6 +28,7 @@ const SampleComponent = () => {
Firestore app name: {firestore?.app.name}
Auth app name: {auth?.app.name}
Functions app name: {functions?.app.name}
+
Storage app name: {storage?.app.name}
); }; @@ -37,6 +40,7 @@ it("suite app instance should be accessible", async () => { firestore={firestore} auth={auth} functions={functions} + storage={storage} > , @@ -54,4 +58,7 @@ it("suite app instance should be accessible", async () => { expect(screen.getByText("Functions app name: [DEFAULT]").innerHTML).toBe( "Functions app name: [DEFAULT]", ); + expect(screen.getByText("Storage app name: [DEFAULT]").innerHTML).toBe( + "Storage app name: [DEFAULT]", + ); }); diff --git a/src/providers/FirebaseSuiteProvider.tsx b/src/providers/FirebaseSuiteProvider.tsx index 33882e8..b0d14ad 100644 --- a/src/providers/FirebaseSuiteProvider.tsx +++ b/src/providers/FirebaseSuiteProvider.tsx @@ -7,10 +7,12 @@ import { FirebaseApp } from "firebase/app"; import { Auth } from "firebase/auth"; import { Firestore } from "firebase/firestore"; import { Functions } from "firebase/functions"; +import { FirebaseStorage } from "firebase/storage"; import { FirebaseAuthProvider, FirebaseFunctionsProvider, FirebaseProvider, + FirebaseStorageProvider, FirestoreProvider, } from "."; import { NodeComponent } from "../types"; @@ -31,6 +33,7 @@ type FirebaseSuiteProviderProps = { firestore?: Firestore; auth?: Auth; functions?: Functions; + storage?: FirebaseStorage; } & NodeComponent; export const FirebaseSuiteProvider = ({ @@ -38,6 +41,7 @@ export const FirebaseSuiteProvider = ({ firestore, auth, functions, + storage, children, }: FirebaseSuiteProviderProps) => { return ( @@ -65,7 +69,16 @@ export const FirebaseSuiteProvider = ({ )} > - {children} + ( + + {c} + + )} + > + {children} + diff --git a/src/providers/index.ts b/src/providers/index.ts index 653f9c2..c1df9bd 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -10,3 +10,5 @@ export * from "./FirestoreProvider"; export * from "./FirebaseAuthProvider"; export * from "./FirebaseFunctionsProvider"; + +export * from "./FirebaseStorageProvider";