⚠️ This is a legacy version of the React tutorial. Please visit docs.meteor.com/tutorials/react for the current version.

3: Using GraphQL to get Data

3.1 Query to Get Tasks

To write queries and mutations you are going to use gql and so install graphql-tag.

meteor npm install graphql-tag

Now let’s start from the server side by creating a new TaskSchema and a new TaskResolvers to our tasks.

imports/api/graphql.js

..
import { TasksCollection } from "../db/TasksCollection";
..
const TaskSchema = `
  type Query {
    tasks: [Task]
  } 
  
  type Task {
    _id: ID!
    text: String
    createdAt: String
    isChecked: Boolean
    user: User
  }
`;

const TaskResolvers = {
  Query: {
    async tasks(root, args, { userId }) {
      if (!userId) {
        return null;
      }
      return TasksCollection.find({ userId }, { sort: { createdAt: -1 } });
    },
  },
  Task: {
    user({ userId }) {
      return Meteor.users.findOne(userId);
    }
  }
};

startGraphQLServer({
  typeDefs: [UserSchema, TaskSchema],
  resolvers: [UserResolvers, TaskResolvers],
  log
});

For the client side, let’s create a query to get our tasks and update our App to merge the data coming from the GraphQL with the data coming from the subscription.

To call a query we can use the hook useQuery from @apollo/react-hooks.

imports/ui/App.jsx

..
import { useQuery } from "@apollo/react-hooks";
import gql from 'graphql-tag';
..
const tasksQuery = gql`
  query Tasks {
    tasks {
      _id
      text
    }
  }
`;

export const App = () => {
  ..
  const  {  data, loading } = useQuery(tasksQuery)

  const { tasksStatus, pendingTasksCount, isLoading } = useTracker(() => {
    const noDataAvailable = { tasksStatus: [], pendingTasksCount: 0 };
    ..
    if (!handler.ready() || loading) {
      return { ...noDataAvailable, isLoading: true };
    }
    const tasksStatus = TasksCollection.find(
    ..
    return { tasksStatus, pendingTasksCount };
  });
  ..
  const tasksData = data && data.tasks || [];
  const tasks = tasksData.map(({ _id, ...rest }) => ({
    _id,
    ...rest,
    isChecked: (tasksStatus.find(t => t._id === _id) || {}).isChecked,
  }));
  ..

Notice that in the query tasksQuery we just need the _id and the text as the isChecked is coming from the subscription, and we used the createdAt to sort the tasks on the server side, so we don’t need it in the client side.

Now our tasks, fetched with GraphQL, have their status again, and the tasks’ status was fetched with a subscription. When you do the changes and refresh your app again, all the tasks’ text should now be visible.

3.2 Refetching Queries

Everything seems to be working, but our app has a big problem.

If you try to add or remove a task, nothing will happen until you refresh the page after the action.

Everything was working before (and still it is with the toggle) because we were getting the tasks using the useTracker that will automatically fetch the data again if something changes. But now, we’re getting the tasks with GraphQL and after an action that changes something in the background, we need a way to call the query again. With GraphQL, we have a couple of ways of doing it.

For now, let’s use the function refetch that’s returned from the hook useQuery, alongside the data and the loading.

We’ll need to pass forward this function to the TaskForm so we can call the query again as soon as the user adds a new task, and we need to call this function well someone tries to delete a task. The easiest way will be to move the delete function inside the App component.

imports/ui/App.jsx

..
export const App = () => {
  ..
  const  { loading, data, refetch } = useQuery(tasksQuery)
  ..
  const logout = () => Meteor.logout();

  const deleteTask = ({ _id }) => {
    Meteor.call('tasks.remove', _id);
    refetch();
  };

  return (
    <div className="app">
      ..
      <div className="main">
            ..
            <TaskForm refetch={refetch}/>
            ..
      </div>
    </div>
  );
};

Now for the TaskForm:

imports/ui/TaskForm.jsx

..
export const TaskForm = ({ refetch }) => {
  ..
  const handleSubmit = e => {
    e.preventDefault();

    if (!text) return;

    Meteor.call('tasks.insert', text);

    setText('');
    refetch();
  };
  ..
};

Everything should be working again.

Review: you can check how your code should be at the end of this step here

In the next step we’ll see how to use a mutation.

Edit on GitHub
// search box