Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Architecture] @tanstack/react-query best practices #96

Open
reboottime opened this issue May 22, 2024 · 3 comments
Open

[Architecture] @tanstack/react-query best practices #96

reboottime opened this issue May 22, 2024 · 3 comments

Comments

@reboottime
Copy link
Owner

reboottime commented May 22, 2024

Overview

What this article is about

This article summarizes the best practices for designing a React app using @tanstack/react-query, under the premise that your restful APIs are well designed. If your API is not resource-oriented and driven by client, consider using GraphQL instead.

The react query version discussed in this article is 5.37.1.



Why @tankstac/react-query?

Upon watching this youtube video, presented by @tanstack/react-query author Tanner Linsley, you will get a brief idea about what problems pushed the born of @tanstack/react-query.

And get what bens @tanstack/react-query brings to u via reading the motivation article



React Query Best Practices

@reboottime
Copy link
Owner Author

reboottime commented May 23, 2024

First Practice: Group services and queries by resources

  • Guidelines

    • Design and name your service member functions around resources.

    • Group your React Query query/mutation functions around resources, and mirror the service member names for your React Query hooks.

    • Group your React Query keys using an array, as query key drives query, to know more, read this

    • Export variables

      • Other than the queries/mutations hooks

      • export both the HTTP client instance and the grouped query keys

      • The why: bring convenience to where you use it

        • you may want to invalidate your at the places where you use that query
        • you may want to prefetch data at a list page
        <Link
          className="block card card--link"
          onMouseEnter={() => {
            queryClient.prefetchQuery({
              queryKey: credentialQueryKeys.orders(order.id),
              queryFn: () => ordersClient.geOrderById(order.id),
            });
          }}
          to={`/orders/${order.id}`}
        >
          <OrderCard {...order} className="h-[152px]" />
        </Link>
  • Example

    • orders.services.ts
interface GetAllOrdersParams {
  status?: "pending" | "processing" | "completed" | "cancelled";
  sortBy?: "createdAt" | "totalAmount";
  sortOrder?: "asc" | "desc";
  page?: number;
  limit?: number;
}

class Orders {
  private httpClient: HttpClient;

  constructor(baseUrl: string) {
    this.httpClient = new HttpClient(baseUrl);
  }

  // Query all orders
  async getAllOrders(params?: GetAllOrdersParams): Promise<Order[]> {
    //   your code
  }

  // Query order by ID
  async getOrderById(orderId: string): Promise<Order> {
    //   your code
  }

  // Create a new order
  async createOrderById(orderData: OrderData): Promise<Order> {
    // Your code
  }

  // Update an existing order
  async updateOrderById(
    orderId: string,
    updateData: Omit<Partial<Order>, "id">
  ): Promise<Order> {
    // Your code
  }

  // Delete an order
  async deleteOrderById(orderId: string): Promise<void> {
    //   your code
  }
}
  • orders.queries.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Orders, GetAllOrdersParams, OrderData, Order } from "./order.services";

import queryClient from 'setup/query-client';

export const ordersHttpClient = new Orders("your-base-url");

export const ordersQueryKeys = {
    orders: ['orders'],
    allOrders: (params) => [...ordersQueryKeys.orders, parmas],
    orderById: (orderId:string) => [ordersQueryKeys.orders, orderId],
}


// export data prefetch method, to preload data. Corresponding http request will not be made if data already cahced
export const prefetchAllOrders = (params: Record<string, any>) {
    return queryClient.fetchQuery({
        queryKey: ordersQueryKeys.allOrders(params),
        queryFn: () => ordersHttpClient.getOrders(params)
    })
}

// export data prefetch method, to preload data. Corresponding http request will not be made if data already cahced
export const prefetchOrderById = (orderId: Order['id']) => {
    return queryClient.fetchQuery({
        queryKey: ordersQueryKeys.orderById(orderId),
        queryFn: () => ordersHttpClient.getOrderById(orderId)
    });
}

export const invalidateOrders = () => {
    return queryClient.invalidate
}

const useAllOrders = (params?: GetAllOrdersParams) => {
  return useQuery(["orders", params], () =>
    ordersHttpClient.getAllOrders(params)
  );
};

const useOrderById = (orderId: string) => {
  return useQuery(["order", orderId], () =>
    ordersHttpClient.getOrderById(orderId)
  );
};

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

  return useMutation(
    (orderData: OrderData) => ordersHttpClient.createOrderById(orderData),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["orders"]);
      },
    }
  );
};

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

  return useMutation(
    ({
      orderId,
      updateData,
    }: {
      orderId: string;
      updateData: Omit<Partial<Order>, "id">;
    }) => ordersHttpClient.updateOrderById(orderId, updateData),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["orders"]);
        queryClient.invalidateQueries(["order"]);
      },
    }
  );
};

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

  return useMutation(
    (orderId: string) => ordersHttpClient.deleteOrderById(orderId),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["orders"]);
        queryClient.invalidateQueries(["order"]);
      },
    }
  );
};

@reboottime
Copy link
Owner Author

reboottime commented May 23, 2024

Configure Wisely

Configure your query and query client default options based on your needs:

  • staleTime: Set the time after which the cached data is considered stale and needs to be refetched.
  • retry: Define the strategy for retrying failed requests.
  • `keepPreviousData: Decide whether to keep the previous data while new data is being fetched.

For example, for a real-time visualization monitoring dashboard, you may want:

  • refetchOnWindowFocus: Refetch data when the browser tab becomes visible.
  • staleTime: Set to the desired refresh frequency for real-time data.
  • remind user about network connection status, via React Query online manager
import { onlineManager } from '@tanstack/react-query'

@reboottime
Copy link
Owner Author

reboottime commented May 23, 2024

For Realtime data update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant