DEV Community

loading...

Managing Query Variables State via Apollo-Client's "Reactive Variables"

Pierre
・3 min read

With all the power that apollo-client brings to your application development and environment, there are very minimal challenges that you need to answer, like managing variables of a graphql query.

As you obviously know query variables are equivalent to the REST's post method payload, which you would've sent to the server as parameters which result may heavily depend on.

Let's go about a real-life practical example, here's a query variables object we might embark on:

{
    "limit": 10,
    "offset": 0,
    "search": "",
    "filters": {
        "vendors": [],
        "contracts": []
    }
}
Enter fullscreen mode Exit fullscreen mode

We have limit and offset responsible for pagination, search as the keyword to filter a list(returned by the API for sure), and filters which is responsible to filter out items by their other relations or attributes. So three fields( maybe more if you consider filters children) and each of them controlled by totally separate components, meaning three different *ChangeHandlers.

If you come from a non-apollo/graphql background as I do, you'd probably go about managing this object with some solution like react's very useReducer hook.
It would look something like this:

// component.js
const variablesInitialState = {
  "limit": 10,
  "offset": 0,
  "search": "",
  "filters": {
      "vendors": [],
      "contracts": []
  }
}
// 
function variablesReducer(state, action) {
  switch (action.type) {
    case 'setSearch':
      //non-relevant logic
      //...
    case 'setPagination':
      //non-relevant logic
      //...
    case 'setFilters':
      //non-relevant logic
      //...
    default:
      return state;
  }
}

// ...
function Component(props) {
const [variablesState, variablesDispatch] = useReducer(
    variablesReducer,
    variablesInitialState,
  );

// ...
const {
    data: teamMembersData,
    networkStatus: membersNetStat,
    refetch: membersRefetch,
  } = useQuery(GET_TEAM_MEMBERS, {
    variables: {
      ...variablesState,
    })
}
Enter fullscreen mode Exit fullscreen mode

As much as it may seem logical to do so, but the maintenance overhead will accumulate over time while you are changing your state and then call refetch [too], every time.

So maybe I could quit being explicit about calling refetch while it seems to be the most probable next move.

So that's where Apollo-Client's Reactive Variables comes into play.

As it's put by Apollo Blog Reactive Variables are:

containers for variables that we would like to enable cache reactivity for. Using the small API, we can either: 1- set the value by passing in an argument — var(newValue) 2- get the value by invoking the reactive variable — const currentValue = var()

Utilizing that we could create a little hook containing the logic for managing the variables state like so:

// hook.js
import { makeVar } from '@apollo/client';

// Create the initial value
const tmqvarsInitial = {
  search: '',
  filters: { vendors: [] },
  limit: 20,
  offset: 0,
};

// Create the teamMembersQuery var and initialize it with the initial value
export const teamMembersQVars = makeVar(tmqvarsInitial);

// expose the operations done on the state
export function useTeamMemberQVars(teamMembersQVars) {
  const setSearch = text => {
    const tmqvars = teamMembersQVars();
    const updatedTmqvars = {
      ...tmqvars,
      search: text,
    };
    teamMembersQVars(updatedTmqvars);
  };

  const setFilters = filters => {
    const tmqvars = teamMembersQVars();
    const updatedTmqvars = {
      ...tmqvars,
      filters,
    };
    teamMembersQVars(updatedTmqvars);
  };

  const setPagination = ([limit, offset]) => {
    const tmqvars = teamMembersQVars();
    const updatedTmqvars = {
      ...tmqvars,
      limit,
      offset,
    };
    teamMembersQVars(updatedTmqvars);
  };

  return {
    setSearch,
    setFilters,
    setPagination,
  };
}
Enter fullscreen mode Exit fullscreen mode

to be used like:

// component.js
import { useReactiveVar } from '@apollo/client';
import { teamMembersQVars, useTeamMemberQVars } from './useTeamMembersQVars';
// ...
function Component(props) {
// ...
// reactive variables
  const { setSearch, setFilters, setPagination } = useTeamMemberQVars(
    teamMembersQVars,
  );
// ...
// query def
const { data, error, networkStatus } = useQuery(GET_TEAM_MEMBERS, {
    variables: useReactiveVar(teamMembersQVars),
  });
}
}
Enter fullscreen mode Exit fullscreen mode

This way you just worry about calling operations (e.g. setSearch) inside your *ChangeHandlers and your query will rerun automatically because the hook useReactiveVar from Apollo-Client will rerender the components if the reactive variable accompanied by them has been through a change.

P.S.

This is just a solution to a challenge that may have thousands of solutions. I am a beginner and grateful for your suggestions. Please do not hold back.

Cheers,
To all who never quit being a beginner. 🍻 🍻

Discussion (2)

Collapse
thomas_tvedt_3a22bb6c68c7 profile image
Thomas Tvedt

Hey, excellent stuff! I just started a new app using apollo client 3 and automatically reached for redux for local state. Considering switching to "reactive vars", but had two concerns:

  1. I currently have some "reducer logic", how can I mirror this with reactive vars?
  2. Is it best practice to use reactive vars for paging?

I think your solution above looks great, good job :)

Collapse
yogesnsamy profile image
yogesnsamy

Thank you for this. Was looking for an example on how to use a reactive var as a variable when calling an API. Now I know the key is useReactiveVar