Quick Introduction to Strapi Headless CMS for Ionic ReactJS Mobile App w/GraphQL

Aaron K Saunders - Feb 8 '21 - - Dev Community

Alt Text

Overview

I started a series on using strapi.io as a headless CMS for Ionic Framework application written in ReactJS. In all of the earlier videos, I was using the REST API to access the content in the CMS and I wanted to try using the GraphQL API that is provided.

Strapi is the leading open-source headless CMS. It’s 100% Javascript, fully customizable and developer-first.

This post goes along with the video I created showing how to refactor the code from the REST API to start using the GraphQL API.

This post should be seen as a companion document to the video and source code

Lets Go

Install libraries we need to get graphql integrated with strapi.

npm install apollo-upload-client
npm i --save-dev @types/apollo-upload-client
npm install graphql @apollo/client
Enter fullscreen mode Exit fullscreen mode

Now that we have the libraries, let's set up the client in index.tsx

First we import the necessary libraries

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";
Enter fullscreen mode Exit fullscreen mode

The create the client from new AplolloClient(), since we are uploading files we are using the createUploadLink function to create the link associated with the strapi server; We will also be using the in memory cache

const client = new ApolloClient({
  link: createUploadLink({
    uri: "http://localhost:1337/graphql",
  }),
  cache: new InMemoryCache(),
});
Enter fullscreen mode Exit fullscreen mode

Finally, wrap the whole app with the ApolloProvider which will give us access to the client in the application.

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Loading All The ImagePosts

We are going to load all of the posts because they are needed for the first page of the app Home.tsx

We need to import the libraries, we're going to use to support the useQuery hook

import { gql, useQuery } from "@apollo/client";
Enter fullscreen mode Exit fullscreen mode

Let's set up the query which was tested in the playground; we use this to get all of the ImagePosts and the properties from the object we need to render in the user interface.

const IMAGE_POST_QUERY = gql`
  query getAll {
    imagePosts {
      id
      title
      body
      image {
        name
        id
        url
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Now we can utilize the useQuery hook to get the data, provide us with some loading information and an error object if necessary.

const {
  loading,
  error,
  data,
} = useQuery(IMAGE_POST_QUERY);
Enter fullscreen mode Exit fullscreen mode

Now let's move on to the template and start with adding the IonLoading component which uses the loading property from above.

<IonLoading isOpen={loading} message="Loading..."/>
Enter fullscreen mode Exit fullscreen mode

The query returns the data with the property imagePosts because that is what I specified in the query; we loop through that property to render the results.

<IonList>
  {!loading && data?.imagePosts?.map((p: any) => {
    return (
      <IonItem key={p.id}>
        <IonLabel>
          <h1 className="ion-text-wrap">{p.title}</h1>
          <h2 className="ion-text-wrap">{p.body}</h2>
          <p className="ion-text-wrap">{p.image?.name}</p>
          <div>
            <IonImg
              src={`http://localhost:1337${p.image?.url}`}
            ></IonImg>
          </div>
        </IonLabel>
      </IonItem>
    );
  })}
</IonList>
Enter fullscreen mode Exit fullscreen mode

Adding A New ImagePost

The same process as before when querying the data, we will use when mutating the data. First we define the mutation we will use with the useMutation hook and pass it the appropriate parameters.

Like before this is a two-step process, upload the file and then add the post

We will upload the selected image using this upload mutation constant UPLOAD_MUTATION

const UPLOAD_MUTATION = gql`
  mutation($file: Upload!) {
    upload(file: $file) {
      name
      id
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Next we are setting the hook up with the name of the method we will use addImageGQL. We will need the loading status for the component and then finally we pass in the query.

const [
  addImageGQL, 
  { loading: loadingUpload }
] = useMutation(UPLOAD_MUTATION);
Enter fullscreen mode Exit fullscreen mode

In order to call the function and upload the file, we use the addImageGQL method like this. The file parameter is from the local state variable we defined to hold the file object returned from the input form.

const {
  data: imageData
} = await addImageGQL({ variables: { file } });
Enter fullscreen mode Exit fullscreen mode

This will upload the file for us and provide us with the id of the uploaded file to associate with the ImagePost. We can access it like this.

imageData.upload.id
Enter fullscreen mode Exit fullscreen mode

Now that we have the image in the CMS we can get an id to associate with the imagePost and save the whole document.

First we need the imagePost mutation; a constant UPLOAD_IMAGE_POST_MUTATION notice that all of the parameters we need for the mutation are the fields we captured in the input form in AddItem.tsx; We also can specify the fields we need to be returned from the mutation.

const UPLOAD_IMAGE_POST_MUTATION = gql`
  mutation createImagePost($title: String, $body: String, $image: ID) {
    createImagePost(
      input: { data: { title: $title, body: $body, image: $image } }
    ) {
      imagePost {
        id
        title
        body
        image {
          id
          url
          name
        }
        created_at
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

To upload the post we use the useMutation hook and pass along the id of the image and the title and body from the input form.

const [
  addImagePostGQL, 
  { loading: loadingImagePost }
] = useMutation( UPLOAD_IMAGE_POST_MUTATION);
Enter fullscreen mode Exit fullscreen mode

here is the use of the hook in action

const { data: postData } = await addImagePostGQL({
  variables: {
    title,
    body,
    image: imageData.upload.id,
  },
});
Enter fullscreen mode Exit fullscreen mode

At this point, you should be able to see that the document has been added to the strapi CMS.

to handle optimistic load of the imagePosts, meaning load the imagePost in the local cache; we can push the new record into the cache using the following code.

const [
  addImagePostGQL, 
  { loading: loadingImagePost }
] = useMutation(
  UPLOAD_IMAGE_POST_MUTATION,
  {
    update: (cache, { data: { createImagePost } }) => {
      const { imagePost } = createImagePost;
      // get the posts from the cache...
      const currentData: any = cache.readQuery({ query: IMAGE_POST_QUERY });
      // add the new post to the cache & write results back to cache
      cache.writeQuery({
        query: IMAGE_POST_QUERY,
        data: {
          imagePosts: [...currentData?.imagePosts, imagePost],
        },
      });
    },
  }
);
Enter fullscreen mode Exit fullscreen mode

Conclusion

As stated above, this is meant to accompany the video so please take a look at the videos in the series, review the document and if it is still not clear, leave a comment.

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