1

I'm trying to encapsulate hooks in a class component so that it's easier to find, categorize and call hooks. Would this be a good and viable approach to encapsulate react query hooks in a class as the example provided bellow?

const keys = {
  receipts: (filter?: ReceiptFilter) => ['receipts', filter],
  receipt: (id: string) => ['receipt', id]
};

class ReceiptQueries {
  public useReceipts = (filter?: ReceiptFilter) => {
    const dispatch = useDispatch();
    return useQuery({
      queryKey: keys.receipts(filter),
      queryFn: () => ReceiptApi.getReceipts(filter),
    });
  }

  public useReceipt = (id: string) => {
    return useQuery({
      queryKey: keys.receipt(id),
      queryFn: () => ReceiptApi.getReceipt(id),
    });
  }

  public useAddReceiptMutation = () => {
    const queryClient = useQueryClient();
    return useMutation({
      mutationFn: ({ receipt }) => ReceiptApi.addReceipt(receipt),
      onSuccess: () => {
        queryClient.invalidateQueries(keys.receipts());
      }
    });
  }

  public useUpdateReceiptMutation = () => {
    return useMutation({
      mutationFn: ({ receipt }) => ReceiptApi.updateReceipt(receipt),
    });
  }

  public useDeleteReceiptMutation = () => {
    return useMutation({
      mutationFn: ({ receiptId }) => ReceiptApi.deleteReceipt(receiptId),
    });
  }
}

export default new ReceiptQueries();

And then I just import the class into the component and call the queries I need.

import ReceiptQueries from 'ReceiptQueries'

const App: React.FC = () => {
  const { data: allReceipts } = ReceiptQueries.useReceipts();

  return (
    <div>
      {allReceipts.map(receipt => RenderReceipt...)}
    </div>
  )
}

I have tried it out and it seems to be working, no errors so far. But I'm not sure if I'm breaking any rules of hooks or if this method could cause any problems in the future? If it's not suitable to use this approach, could I instead use a function that encapsulate all the hooks in the given category?

Personally I like this approach, but I wont be using it if it may cause errors later on.

HellYeah
  • 11
  • 1

1 Answers1

3

It will work but it's too verbose. The ReceiptQueries class does not do many things but just a namespace.

You can implement this by using named exports and namesapce import. JS File with an import or export keyword usually is called "module", you can give this module a more meaningful name to indicate its function.

Less code, less error-prone. Keep it simple and save time.

E.g.

ReceiptQueries.js:

export const useReceipts = (filter?: ReceiptFilter) => {
    const dispatch = useDispatch();
    return useQuery({
      queryKey: keys.receipts(filter),
      queryFn: () => ReceiptApi.getReceipts(filter),
    });
  }

export useReceipt = (id: string) => {
    return useQuery({
      queryKey: keys.receipt(id),
      queryFn: () => ReceiptApi.getReceipt(id),
    });
  }

// rest are same
import * as ReceiptQueries from './ReceiptQueries';

const TestComp = () => {
  const receipts = ReceiptQueries.useReceipts();
  return null;
}

Also, see ES6 class static method vs function

Lin Du
  • 88,126
  • 95
  • 281
  • 483