Nextjs revalidateTags of a query called in a server action that is called by a client component

I’m facing a scenario that I can’t solve.

I’m working on a NextJS 14 application, that is querying data from an external GraphQL API.

I have a Customers page, that display all of our customers.

In order to fetch our Customers, I’m using a server action get-customers.ts :

"use server";

import { RequestResult } from "@/common/types/request.interface";
import { request } from "@/common/utils/http/request";
import {
  CustomersDocument,
  CustomersQuery,
  CustomersQueryVariables,
} from "@/graphql/generated/graphql";

export default async function getCustomers(
  variables: CustomersQueryVariables
): Promise<RequestResult<CustomersQuery>> {
  console.log("About to query customers");
  const { result }: { result: RequestResult<CustomersQuery> } = await request<
    CustomersQuery,
    CustomersQueryVariables
  >(CustomersDocument, variables, ["customers"]);

  return result;
}

In this server action, I’m calling request, which is my helper for fetching, and passing it some tags.

Then I have my page. In order to use my external API’s pagination, filtering and sorting feature, I have to re-call my query when my filters change, so I’m using useEffect, and therefore I need to declare my page as a "use client" page.

Here is my page (with truncated useless code) :

"use client";

export default function Customers() {
  const [filters, setFilters] = useState<FilterInput>({
    search: "",
    orderBy: "id",
    sortDirection: "desc",
  });
  const [paginationInput, setPaginationInput] = useState<PaginationInput>({
    page: 1,
    perPage: 10,
  });

  const handleFilterChange = useCallback((newFilters: FilterInput) => {
    setFilters((prev) => ({ ...prev, ...newFilters }));
  }, []);

  const handlePageChange = useCallback((newPage: number) => {
    setPaginationInput((prev) => ({ ...prev, page: newPage }));
  }, []);

  const [data, setData] = useState<CustomersQuery | null>(null);
  const [errors, setErrors] = useState<AppError[] | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      const result: RequestResult<CustomersQuery> = await getCustomers({
        ...filters,
        ...paginationInput,
      });
      setData(result.data);
      setErrors(result.errors ?? null);
    };

    fetchData();
  }, [filters, paginationInput]);

  if (errors) return <div>An error occured !</div>;
  if (!data) return <div>Loading...</div>;

  return (
    <div className="grow flex flex-col gap-6">
      <CreateCustomerSheet />

      <div className="flex gap-2 items-center justify-between">
        <Input
          placeholder="Filter customers..."
          value={filters.search?.toString()}
          onChange={(event) => {
            setPaginationInput((prev) => ({ ...prev, page: 1 }));
            return handleFilterChange({ search: event.target.value });
          }}
          className="max-w-sm bg-background"
        />
        <CreateCustomerSheet />
      </div>
      <DataTable<Partial<CustomerType>>
        columns={columns}
        data={data.customers.data}
        paginationInput={paginationInput}
        paginationInfo={data.customers.paginationInfo}
        onPageChange={handlePageChange}
        filterInput={filters}
        onSortingChange={handleFilterChange}
      />
    </div>
  );
}

As you can see, this page is using a lot of client stuff.

Now, in my <CreateCustomerSheet />, there is a form, that is calling another server action that creates new customers :

export default async function createCustomer(
  _prevState: FormState,
  data: FormData
): Promise<any> {
  try {
    const formData = Object.fromEntries(data);
    const parsed = schema.safeParse(formData);
    console.log("Parsed", parsed);

    if (!parsed.success) {
      let errors = parsed.error.issues.map((issue) => ({
        field: issue.path.join("."),
        message: issue.message,
      }));
      return { errors };
    }

    const { result } = await request<
      CreateCustomerMutation,
      CreateCustomerMutationVariables
    >(CreateCustomerDocument, {
      company_name: parsed.data.company_name,
      email: parsed.data.email,
      address1: parsed.data.address1,
      city: parsed.data.city,
      country: parsed.data.country,
    });

    if (result.data?.createCustomer) {
      console.log("revalidating tag");
      revalidateTag("/customers");
    }

    // if (result.errors) return { message: result.errors[0].message };
  } catch (error) {
    return { message: createAppError("UNKNOWN_ERROR").message };
  }
}

When I create a new customer, I can see my logs in my server, telling me that creation is successfull, that it is revalidating tag, but my query is not re-triggered.

What did I try ?

I tried removing all the client stuff in my page.tsx component in order to make it become a server component. Then when creating customer, I can clearly see my data getting updated.

What should I do ?

How could I make it a server component but keeping track of my user input when filtering ? I don’t understand how I could handle it.

In many examples I can find people querying data from server component before to pass it down to a client component, but I can’t manage to handle this case where I need my filters, pagination and sorting to update when I’m clicking on my datatable or filling my searchbar.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật