Using Context API & ReactFire with ReactJS, Ionic Framework & Capacitor

This is a continuation of a series of blog post showing Firebase ReactFire in action with Ionic Framework React Components. In this post, we will move all of the data interaction with firebase into a separate component using Context API and React Hooks to separate Firebase specific code from the User Interface components of the application.

Setting Up The Context

Add the required imports to the file

// DataContext.tsx
import React from "react";
import { useFirebaseApp, useFirestoreCollectionData } from "reactfire";
import { FIREBASE_COLLECTION_NAME } from "./env";
// type for accessing the data structure for saving in firebase
import { IModalData } from "./components/AddSomethingModal";

Next we can describe the shape of the state as an interface that we will use when setting up the context, it will allow us to use intellisense and errors will get generated when compiling is parameters don’t match what is expected

// DataContext.tsx
interface IState {
dataCollection: null | undefined | any;
addItem: (itemData: IModalData) => Promise<void>;
removeItem: (itemData: IModalData) => Promise<void>;
}
// create the context
export const DataContext = React.createContext<IState | undefined>(undefined);

Next create the context provider, we are using and use the state object to ensure that we get reactive values from the context...

export const DataProvider: React.FC = ({ children }) => {  // the store object
let state = {
// functions and properties associated with the context
// are included as part of the state object here
};
// wrap the app in the provider with the initialized context
return <DataContext.Provider value={state}>{children}</DataContext.Provider>;
};

Finally return the DataContext and then a helper function useDataProvider so we can access the context in the application when we need to

export default DataContext;
export const useDataProvider = () =>
React.useContext<IState | undefined>(DataContext)!;

Filling Out The Context We Created

We need to be able to access the data collection and manipulate the data collection from the context. This means the shape of out state object is as follows

// the store object
let state = {
dataCollection: data,
addItem, // function, adds to collection
removeItem, // function, remove from collection
};

and the function as are implemented as follows, using the firebase code that was previously in the UI components

/**
* @param itemData
*/
const addItem = (itemData: IModalData) => {
return thingsRef.doc().set({ ...itemData });
};
/**
* @param itemData
*/
const removeItem = (itemData: IModalData) => {
return thingsRef.doc(itemData.id).delete();
};

Finally we use the reactFire hooks to get the data collection and to setup the collectionRef that we need for our functions above.

// another reactfire hook to get the firebase app
const thingsRef = useFirebaseApp()
.firestore()
.collection(FIREBASE_COLLECTION_NAME);
// another hook to query firebase collection using
// the reference you created above
const data = useFirestoreCollectionData(thingsRef, { idField: "id" });

Using the DataContext In the App

We want to be specific where we wrap the app using the <DataProvider>, since we have separated out the public components, that is where we will start.

// App.tsx
const PrivateRoutes: React.FunctionComponent = () => {
return (
<IonRouterOutlet>
<Route exact path="/home">
<DataProvider>
<Home />
</DataProvider>
</Route>
<Redirect exact path="/" to="/home" />
</IonRouterOutlet>
);
};

Now inside of the <Home /> we have access to the context information.

We start with getting the state information from the context using the helper function we provided

const { 
addItem,
removeItem,
dataCollection
} = useDataProvider();

Removing An Item

The function utilizing the context information

/**
* @param item IModalData
*/
const removeSomething = (item: IModalData) => {
removeItem(item)
.then(() => showAlert("Success"))
.catch((error: any) => {
showAlert(error.message, true);
});
};

In the render method we using the dataCollection property to access the list of objects and the removeSomething function to access the code to remove the item when list entry is clicked

<IonList>
{dataCollection.map((e: any) => {
return (
<IonItem key={e.id} onClick={() => removeSomething(e)}>
<IonLabel className="ion-text-wrap">
<pre>{JSON.stringify(e, null, 2)}</pre>
</IonLabel>
</IonItem>
);
})}
</IonList>

Adding An Item

The function that is utilizing the context information

/**
* @param response IModalResponse
*/
const addSomething = async (response: IModalResponse) => {
setShowModal(false);
if (response.hasData) {
alert(JSON.stringify(response.data));
addItem(response.data!)
.then(() => showAlert("Success"))
.catch((error: any) => {
showAlert(error.message, true);
});
} else {
showAlert("User Cancelled", true);
}
};

Integration in the render method

{/* ionic modal component */}
<IonModal isOpen={showModal} onDidDismiss={() => setShowModal(false)}>
{/* our custom modal content */}
<AddSomethingModal
onCloseModal={(data: IModalResponse) => addSomething(data)}
/>
</IonModal>

Source Code

Project available on GitHub, please look for the specific tag associated with this blog post.

DC based software agency utilizing #Javascript, #Typescript, #HTML5, #Ionicframework, #NodeJS, #Firebase to build web & mobile solutions. https://buff.ly/300Zru

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store