Skip to content

js2me/mobx-tanstack-query

Repository files navigation

logo

mobx-tanstack-query

NPM version test status build status npm download bundle size

MobX wrapper for Tanstack Query Core package

What package supports

Class wrapper for @tanstack-query/core queries with MobX reactivity

Usage

Create an instance of MobxQuery with queryKey and queryFn parameters

const query = new MobxQuery({
  queryClient,
  abortSignal, // Helps you to automatically clean up query  
  queryKey: ['pets'],
  queryFn: async ({ signal, queryKey }) => {
    const response = await petsApi.fetchPets({ signal });
    return response.data;
  },
});  

Features

enableOnDemand option

Query will be disabled until you request result for this query
Example:

const query = new MobxQuery({
  //...
  enableOnDemand: true
});
// happens nothing
query.result.data; // from this code line query starts fetching data

This option works as is if query will be "enabled", otherwise you should enable this query.

const query = new MobxQuery({
  enabled: false,
  enableOnDemand: true, 
  queryFn: () => {},
});
query.result.data; // nothing happened because query is disabled.   

But if you set enabled as true and option enableOnDemand will be true too then query will be fetched only after user will try to get access to result.

const query = new MobxQuery({
  enabled: true,
  enableOnDemand: true, 
  queryFn: () => {},
});
...
// query is not fetched
...
// query is not fetched
query.result.data; // query starts execute the queryFn

dynamic options

Options which can be dynamically updated for this query

const query = new MobxQuery({
  // ...
  options: () => ({
    enabled: this.myObservableValue > 10,
    queryKey: ['foo', 'bar', this.myObservableValue] as const,
  }),
  queryFn: ({ queryKey }) => {
    const myObservableValue = queryKey[2];
  }
});

dynamic queryKey

Works the same as dynamic options option but only for queryKey

const query = new MobxQuery({
  // ...
  queryKey: () => ['foo', 'bar', this.myObservableValue] as const,
  queryFn: ({ queryKey }) => {
    const myObservableValue = queryKey[2];
  }
});

P.S. you can combine it with dynamic (out of box) enabled property

const query = new MobxQuery({
  // ...
  queryKey: () => ['foo', 'bar', this.myObservableValue] as const,
  enabled: ({ queryKey }) => queryKey[2] > 10,
  queryFn: ({ queryKey }) => {
    const myObservableValue = queryKey[2];
  }
});

method start(params)

Enable query if it is disabled then fetch the query.

method update()

Update options for query (Uses QueryObserver.setOptions)

hook onDone()

Subscribe when query has been successfully fetched data

hook onError()

Subscribe when query has been failed fetched data

method invalidate()

Invalidate current query (Uses queryClient.invalidateQueries)

method reset()

Reset current query (Uses queryClient.resetQueries)

method setData()

Set data for current query (Uses queryClient.setQueryData)

property isResultRequsted

Any time when you trying to get access to result property this field sets as true
This field is needed for enableOnDemand option
This property if observable

property result

Observable query result (The same as returns the useQuery hook)

About enabled

All queries are enabled (docs can be found here) by default, but you can set enabled as false or use dynamic value like ({ queryKey }) => !!queryKey[1]
You can use update method to update value for this property or use dynamic options construction (options: () => ({ enabled: !!this.observableValue }))

About refetchOnWindowFocus and refetchOnReconnect

They will not work if you will not call mount() method manually of your QueryClient instance which you send for your queries, all other cases dependents on query stale time and enabled properties.
Example:

import { hashKey, QueryClient } from '@tanstack/query-core';

const MAX_FAILURE_COUNT = 3;

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: true,
      queryKeyHashFn: hashKey,
      refetchOnWindowFocus: 'always',
      refetchOnReconnect: 'always',
      staleTime: 5 * 60 * 1000,
      retry: (failureCount, error) => {
        if ('status' in error && Number(error.status) >= 500) {
          return MAX_FAILURE_COUNT - failureCount > 0;
        }
        return false;
      },
    },
    mutations: {
      throwOnError: true,
    },
  },
});

queryClient.mount(); // enable all subscriptions for online\offline and window focus/blur

Class wrapper for @tanstack-query/core mutations with MobX reactivity

Usage

Create an instance of MobxMutation with mutationFn parameter

const mutation = new MobxMutation({
  queryClient,
  abortSignal, // Helps you to automatically clean up mutation  
  mutationFn: async ({ signal, queryKey }) => {
    const response = await petsApi.createPet({ name: 'Fluffy' }, { signal });
    return response.data;
  },
});  

Features

method mutate(variables, options?)

Runs the mutation. (Works the as mutate function in useMutation hook)

hook onDone()

Subscribe when mutation has been successfully finished

hook onError()

Subscribe when mutation has been finished with failure

method reset()

Reset current mutation

property result

Observable mutation result (The same as returns the useMutation hook)

See docs for MobxQuery

This is the same entity as QueryClient from @tanstack-query/core package, but has a bit improvenments like hooks and configurations for Mobx* like entities

InferQuery, InferMutation, InferInfiniteQuery types

This types are needed to infer some other types from mutations\configs.

type MyData = InferMutation<typeof myMutation, 'data'>
type MyVariables = InferMutation<typeof myMutation, 'variables'>
type MyConfig = InferMutation<typeof myMutation, 'config'>

MobxQueryConfigFromFn, MobxMutationConfigFromFn, MobxInfiniteQueryConfigFromFn

This types are needed to create configuration types from your functions of your http client

const myApi = {
  createApple: (name: string): Promise<AppleDC> => ... 
}

type Config = MobxMutationConfigFromFn<typeof myApi.createApple>

Usage

  1. Install dependencies
pnpm add @tanstack/query-core mobx-tanstack-query
  1. Create QueryClient instance
// @/shared/lib/tanstack-query/query-client.ts
import { hashKey, QueryClient } from '@tanstack/query-core';

const MAX_FAILURE_COUNT = 3;

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: true,
      queryKeyHashFn: hashKey,
      refetchOnWindowFocus: 'always',
      refetchOnReconnect: 'always',
      staleTime: 5 * 60 * 1000,
      retry: (failureCount, error) => {
        if ('status' in error && Number(error.status) >= 500) {
          return MAX_FAILURE_COUNT - failureCount > 0;
        }
        return false;
      },
    },
    mutations: {
      throwOnError: true,
    },
  },
});

queryClient.mount(); // enable all subscriptions for online\offline and window focus/blur
  1. Use it
const petsListQuery = new MobxQuery({
  queryClient,
  queryKey: ['pets'],
  queryFn: async ({ signal, queryKey }) => {
    const response = await petsApi.fetchPets({ signal });
    return response.data;
  },
});

const addPetsMutation = new MobxMutation({
  queryClient,
  mutationFn: async (payload: { petName: string }) => {
    const response = await petsApi.createPet(payload);
    return response.data;
  },

  onSuccess: (data) => {
    rootStore.notifications.push({
      type: 'success',
      title: `Pet created successfully with name ${data.name}`,
    });
    petsListQuery.invalidate();
  },
  onError: (error) => {
    rootStore.notifications.push({
      type: 'danger',
      title: 'Failed to create pet',
    });
  }
});

addPetsMutation.mutate({ petName: 'fluffy' });

Another usage or mobx-tanstack-query/preset

This sub folder is contains already configured instance of QueryClient with this configuration and needed to reduce your boilerplate with more compact way.
Every parameter in configuration you can override using this construction

import { queryClient } from "mobx-tanstack-query/preset";

const defaultOptions = queryClient.getDefaultOptions();
defaultOptions.queries!.refetchOnMount = true;
queryClient.setDefaultOptions({ ...defaultOptions })

P.S. Overriding default options should be written before start whole application

createQuery(queryFn, otherOptionsWithoutFn?)

This is alternative for new MobxQuery(). Example:

import { createQuery } from "mobx-tanstack-query/preset";

const query = createQuery(async ({ signal, queryKey }) => {
  const response = await petsApi.fetchPets({ signal });
  return response.data;
}, {
  queryKey: ['pets'],
})

createMutation(mutationFn, otherOptionsWithoutFn?)

This is alternative for new MobxMutation(). Example:

import { createMutation } from "mobx-tanstack-query/preset";

const mutation = createMutation(async (payload: { petName: string }) => {
  const response = await petsApi.createPet(payload);
  return response.data;
})

createInfiniteQuery(queryFn, otherOptionsWithoutFn?)

This is alternative for new MobxInfiniteQuery(). Example:

import { createInfiniteQuery } from "mobx-tanstack-query/preset";

const query = createInfiniteQuery(async ({ signal, queryKey }) => {
  const response = await petsApi.fetchPets({ signal });
  return response.data;
})

Project examples