Using Context API & ReactFire with Ionic Framework & Capacitor Wrap Up

Aaron K Saunders - Aug 16 '20 - - Dev Community

Overview

To wrap up this series we are going to do the following

  • Demonstrate a pattern I use for passing data into a IonModal Page to use the same components for creating and editing an object.
  • Managing Default Values with React Hook Form & Ionic React Components, React Hook Form is a great library that simplifies forms in ReactJS
  • Updating Data in Firebase, Firestore using ReactFire; added the functionality to the Context we introduced in the last post.

For brevity, I have only included snippets of code here, the full source code is available in the github project linked at end of post

Create and Update Modal Pattern

AddSomethingModal is modified to receive the initialData, this is how we will use the same modal for editing and creating new objects.

<AddSomethingModal
  initialData={showModal.initialData}
  onCloseModal={(data: IModalResponse) => addSomething(data)}
/>
Enter fullscreen mode Exit fullscreen mode

Modified state for showing the AddModal to have an additional property for data, which is passed in if there is an object to edit

// manages the state to determine if we need to open
// the modal or not
const [showModal, setShowModal] = useState<{
  show: boolean;
  initialData?: IModalData;
}>({ show: false });
Enter fullscreen mode Exit fullscreen mode

React Hook Form provides an easy way to set defaultData and it also has a provider to get access to the required functions to properly create components to structure your forms better.

Default Data - https://react-hook-form.com/api/#useForm
useFormContext/FormProvider - https://react-hook-form.com/api/#useFormContext

What we do when editing...
1) Pass data into IonModal using the same state object but now including the initialData values, showModal

// Home.tsx
const editSomething = (item: IModalData) => {
  setShowModal({ show: true, initialData: item });
};
Enter fullscreen mode Exit fullscreen mode

2) Use the useForm hook with the data passed in

// AddSomethingModal.tsx
const methods = useForm({ defaultValues: initialData });
Enter fullscreen mode Exit fullscreen mode

3) The modal is wrapped with the ReactHookForm FormProvider and the methods, properties associated with the form are passed as parameters. This give us access to the information in the child components without passing properties down through the component hierarchy.

<FormProvider {...methods}>
  <form onSubmit={methods.handleSubmit(addTheThing)}>
    <MyIonTextItem 
         labelName="Podcast Name" 
          name="podcastName" />
  </form>
</FormProvider>
Enter fullscreen mode Exit fullscreen mode

4) Access default values in my custom component, since I set the default values when creating the form, the default values will be matched to the IonInput element with the matching name when rendered in MyIonTextItem

// MyIonTextItem.tsx
const { control, errors, register } = useFormContext();
...
<IonItem>
  <IonLabel>{labelName}</IonLabel>
  <Controller
    render={({ onChange }) => (
      <IonInput
        type="text"
        name={name}
        ref={register}
        onIonChange={onChange}
      />
    )}
    control={control}
    name={name}
    rules={{
      required: labelName + " is a required field",
    }}
  />
</IonItem>
Enter fullscreen mode Exit fullscreen mode

Changes to addSomething function where we determine if there is an id, then we will update the item in the database if not, we will add the item

const addSomething = async (response: IModalResponse) => {
  setShowModal({ show: false });
  if (!response.hasData) {
    showAlert("User Cancelled", true);
    return;
  } else {
    try {
      if (response.data?.id) {
        await updateItem(response.data!);
      } else {
        await addItem(response.data!);
      }
      showAlert("Success");
    } catch (error) {
      showAlert(error.message, true);
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Firebase update needed in the DataContext.tsx file to exposed the new function. Since we are using typescript lets add it to the interface IState first.

interface IState {
  dataCollection: null | undefined | any;
  // NEW function for updating items
  updateItem: (itemData: IModalData) => Promise<void>;
  addItem: (itemData: IModalData) => Promise<void>;
  removeItem: (itemData: IModalData) => Promise<void>;
}
Enter fullscreen mode Exit fullscreen mode

Now lets create the function...

const updateItem = (itemData: IModalData) => {
  return thingsRef
          .doc(itemData.id)
          .set({ ...itemData }, { merge: true });
};
Enter fullscreen mode Exit fullscreen mode

Finally lets include it in the data context

// the store object
let state = {
  dataCollection: data,
  addItem,
  updateItem, // <-- NEW
  removeItem,
};

// wrap the application in the provider with the initialized context
return <DataContext.Provider value={state}>{children}</DataContext.Provider>;
Enter fullscreen mode Exit fullscreen mode

New Code for rendering the list with the Line component extracted and all the functions passed in.

The new functionality of editing an item is triggered by clicking on the item in the list.

// Home.tsx
<IonList>
  {dataCollection.map((e: any) => {
    return (
      <Line
        item={e}
        key={e.id}
        edit={editSomething}
        remove={removeSomething}
      />
    );
  })}
</IonList>
Enter fullscreen mode Exit fullscreen mode

We created a separate stateless component that just renders the line items and calls appropriate functions when the line is clicked or the delete button on the line is clicked

// Line.tsx
const Line: React.FunctionComponent<{
  item: IModalData;
  edit: (e: any) => void;
  remove: (e: any) => void;
}> = ({ item, edit, remove }) => {
  return (
    <IonItem>
      <IonLabel className="ion-text-wrap" onClick={() => edit(item)}>
        <pre>{JSON.stringify(item, null, 2)}</pre>
      </IonLabel>
      <IonButton onClick={() => remove(item)} slot="end" color="danger">
        <IonIcon icon={removeCircleOutline} />
      </IonButton>
    </IonItem>
  );
};
export default React.memo(Line);
Enter fullscreen mode Exit fullscreen mode

Source Code

ko-fi

ionic-react-hook-form-react-fire

Last Updated 8/16/2020

Releases and tags coincide with specific blog posts in the series See Blog Series

Sample project motivated by video by David East on Reactfire

Saves The Following Data Structure

I am starting to integrated typescript into my examples since I am seeing questions about types popping up in the forums. The IModalData is the structure of the data that is written to…

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .