JSPM

  • Created
  • Published
  • Downloads 4101
  • Score
    100M100P100Q126246F
  • License MIT

react-query addon for normy - Automatic normalization and data updates for data fetching libraries

Package Exports

  • @normy/react-query
  • @normy/react-query/es/index.js
  • @normy/react-query/lib/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@normy/react-query) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@normy/react-query

npm version gzip size Coverage Status lerna code style: prettier

react-query integration with normy - automatic normalization and data updates for data fetching libraries

Table of content

Introduction ⬆️

This is the official react-query integration with normy, a library, which allows your application data to be normalized automatically. This documentation will cover only react-query specifics, so if you did not already do that, you can find normy documentation here.

Motivation ⬆️

In order to understand what @normy/react-query actually does, it is the best to see an example:

  import React from 'react';
  import {
    QueryClientProvider,
    QueryClient,
    useQueryClient,
  } from '@tanstack/react-query';
+ import { createQueryNormalizer } from '@normy/react-query';

  const queryClient = new QueryClient();
+ createQueryNormalizer(queryClient);

const Books = () => {
  const queryClient = useQueryClient();

  const { data: booksData = [] } = useQuery(['books'], () =>
    Promise.resolve([
      { id: '1', name: 'Name 1', author: { id: '1001', name: 'User1' } },
      { id: '2', name: 'Name 2', author: { id: '1002', name: 'User2' } },
    ]),
  );

  const { data: bookData } = useQuery(['book'], () =>
    Promise.resolve({
      id: '1',
      name: 'Name 1',
      author: { id: '1001', name: 'User1' },
    }),
  );

  const updateBookNameMutation = useMutation({
    mutationFn: () => ({
      id: '1',
      name: 'Name 1 Updated',
    }),
-   onSuccess: mutationData => {
-     queryClient.setQueryData(['books'], data =>
-       data.map(book =>
-         book.id === mutationData.id ? { ...book, ...mutationData } : book,
-       ),
-     );
-     queryClient.setQueryData(['book'], data =>
-       data.id === mutationData.id ? { ...data, ...mutationData } : data,
-     );
-   },
  });

  const updateBookAuthorMutation = useMutation({
    mutationFn: () => ({
      id: '1',
      author: { id: '1004', name: 'User4' },
    }),
-   onSuccess: mutationData => {
-     queryClient.setQueryData(['books'], data =>
-       data.map(book =>
-         book.id === mutationData.id ? { ...book, ...mutationData } : book,
-       ),
-     );
-     queryClient.setQueryData(['book'], data =>
-       data.id === mutationData.id ? { ...data, ...mutationData } : data,
-     );
-   },
  });

  const addBookMutation = useMutation({
    mutationFn: () => ({
      id: '3',
      name: 'Name 3',
      author: { id: '1003', name: 'User3' },
    }),
    // with data with top level arrays, you still need to update data manually
    onSuccess: mutationData => {
      queryClient.setQueryData(['books'], data => data.concat(mutationData));
    },
  });

  // return some JSX
};

const App = () => (
  <QueryClientProvider client={queryClient}>
    <Books />
  </QueryClientProvider>
);

So, as you can see, apart from top level arrays, no manual data updates are necessary anymore. This is especially handy if a given mutation should update data for multiple queries. Not only this is verbose to do updates manually, but also you need to exactly know, which queries to update. The more queries you have, the bigger advantages normy brings.

Installation ⬆️

To install the package, just run:

$ npm install @normy/react-query

or you can just use CDN: https://unpkg.com/@normy/react-query.

You do not need to install @normy/core, because it will be installed as @normy/react-query direct dependency.

Basic usage ⬆️

For the basic usage, see Motivation paragraph. The only thing which you need to actually do is to pass queryClient to createQueryNormalizer. After doing this, you can use react-query as you normally do, but you don't need to make any data updates most of the time anymore.

createQueryNormalizer accepts two arguments:

  • queryClient - this is just a react-query instance you create by new QueryClient(config),
  • normalizerConfig - this is normy config, which you might need to meet requirements for data normalization to work - see explanation for more details. Additionally to normy config, you can also pass normalize option, which is true by default - if you pass false, nothing will be normalized unless explicitely set (see the next paragraph)

Disabling of normalization per query and mutation ⬆️

By default all your queries and mutations will be normalized. That means that for each query there will be normalized representation of its data and for each mutation its response data will be read and all dependent normalized queries will be updated.

However, it does not always make sense to normalize all data. You might want to disable data normalization, for example for performance reason for some extreme big queries, or just if you do not need it for a given query, for instance if a query data will be never updated.

Anyway, you might want to change this globally by passing normalize to createQueryNormalizer:

createQueryNormalizer(queryClient, { normalize: false });

Then, you may override the global default normalize setting per query and mutation. For this, you can use meta option, for example for useQuery:

useQuery(['query-key'], loadData, {
  meta: {
    normalize: false,
  },
});

or for useMutation:

useMutation({
  mutationFn,
  meta: {
    normalize: false,
  },
});

Optimistic updates ⬆️

For normal mutations there is nothing you need to do, normy will inspect response data, calculate dependent queries, update normalized data and update all relevant queries. With optimistic updates though, you need to prepare optimistic data yourself:

useMutation({
  mutationFn: async () => {
    return {
      id: '1',
      name: 'Name updated',
    };
  },
  onMutate: () => {
    return {
      optimisticData: {
        id: '1',
        name: 'Name 1 Updated',
      },
      rollbackData: {
        id: '1',
        name: 'Name',
      },
    };
  },
});

The above code will immediately update all queries which have object with id: 1 in their data. In case of a mutation error, data will be reverted to original rollbackData.

It will work at the same time as a normal mutation too, so on mutation success, all dependent queries will be updated again. If you are sure about the response structure, you might want to disable normalization for this mutation, so that on successful response the normalization won't be repeted unnecessarily:

useMutation({
  mutationFn: async () => {
    return {
      id: '1',
      name: 'Name updated',
    };
  },
  onMutate: () => {
    return {
      optimisticData: {
        id: '1',
        name: 'Name 1 Updated',
      },
      rollbackData: {
        id: '1',
        name: 'Name',
      },
    };
  },
  meta: {
    normalize: false,
  },
});

Garbage collection ⬆️

normy know how to clean after itself. When a query is removed from the store, normy will do the same, removing all redundant information.

Clearing and unsubscribing from updates ⬆️

Typically you won't need it, but in case you do, it is possible to clear normalized data and unsubscribe from react-query events by calling clear method:

const normalizer = createQueryNormalizer(queryClient);

normalizer.clear();

Examples ⬆️

I highly recommend to try examples how this package could be used in real applications.

There are following examples currently:

Licence ⬆️

MIT