Revalidate tags in Next.js for a query invoked within a server function triggered by a client-side component

I'm currently stuck in a situation that's proving to be quite challenging.

Working on an ambitious NextJS 14 application, I've encountered the need to pull data from an external GraphQL API.

Specifically, I have a page dedicated to displaying our valuable customers.

To retrieve this customer data, I rely on a server action named 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;
}

Within this server action, I utilize the helper method 'request' for fetching data and also pass along certain tags.

Then comes my page. To leverage the capabilities of pagination, filtering, and sorting provided by the external API, I must re-query the data when any filters change. Hence, I employ the use of useEffect and declare my page as a "use client" page.

Here's a snippet of my page code (with irrelevant parts omitted):

"use client";

export default function Customers() {
  // Code for handling filters, pagination, etc.
  // ...
}

From what you can see, the page heavily relies on client-side functionalities.

Moving over to the <CreateCustomerSheet /> component, it houses a form that triggers another server action responsible for creating new customers:

export default async function createCustomer(
  _prevState: FormState,
  data: FormData
): Promise<any> {
  try {
    // Logic for parsing and validating the form data
    // ...

    // Making the actual API call to create a new customer
    // ...
    
  } catch (error) {
    // Handling errors gracefully
    // ...
  }
}

Despite seeing successful creation logs in the server, indicating that everything is going smoothly including tag revalidation, the query for refreshing the data does not seem to trigger again.

My Attempts So Far

In an attempt to resolve this issue, I experimented with removing all client-side features from my page.tsx component to transform it into a server component. Surprisingly, upon creating a new customer, I noticed immediate data updates.

What I Need Help With

How can I maintain the essence of a server component while ensuring that user input such as filters, pagination, and sorting are seamlessly updated? It's puzzling me how to address this challenge effectively.

While I've come across examples where data is queried within a server component before being passed down to a client component, I'm struggling to navigate this specific scenario where dynamic user interactions play a crucial role in updating the displayed data.

Answer №1

To enhance the performance of your website, consider transforming your page.tsx into a server component and encapsulating your logic within a client component. Subsequently, import this component in your page:

// CustomerFilter.tsx
"use client"
import { CustomersQuery } from "@/graphql/generated/graphql"
interface CustomerFilterProps {
    allCustomers: CustomersQuery
}
export default function CustomerFilter({ allCustomers }: CustomerFilterProps) {
  // Logic implementation here...
}

// page.tsx
import CustomerFilter from "@/components/CustomerFilter" // module path
export default async function Customers() {
  const customers = await getCustomers(); 
  return <CustomerFilter allCustomers={customers} />
}

By utilizing this structure, any revalidationTag trigger will prompt data to be refreshed from page.tsx, ensuring it remains up-to-date.

Answer №2

By utilizing URLSearchParams, I was able to find a solution.

page.tsx serves as a server component and fetches searchParams :

export default async function Customers({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined };
}) {
  const page = Number(searchParams.page) || 1;
  const perPage = Number(searchParams.perPage) || 10;
  const search = searchParams.search?.toString() || "";
  const orderBy = searchParams.orderBy?.toString() || "id";
  const sortDirection = searchParams.sortDirection?.toString() || "desc";
 const result = await getCustomers({
    page,
    perPage,
    search,
    orderBy,
    sortDirection,
  });

  if (result.errors) return <div>An error occurred!</div>;
  if (!result.data) return <div>No data available.</div>;
 return (
    <div className="grow flex flex-col gap-6">
      <CreateCustomerSheet />
      <Breadcrumb />

      <div className="flex gap-2 items-center justify-between">
        <CustomersList
          filters={filters}
          paginationInput={paginationInput}
          paginationInfo={result.data.customers.paginationInfo}
          data={result.data.customers.data}
        />
      </div>
    </div>

Subsequently, the CustomersList is defined as a "use client" component and updates the URL parameters :

export function CustomersList({
  data,
  paginationInfo,
  filters,
  paginationInput,
}: CustomersListProps) {
  const router = useRouter();
  const currentSearchParams = useSearchParams();

  const handleFilterChange = (newFilters: any) => {
    const params = new URLSearchParams(currentSearchParams);
    Object.entries(newFilters).forEach(([key, value]) => {
      if (value) {
        params.set(key, value.toString());
      } else {
        params.delete(key);
      }
    });
    params.set("page", "1"); // Reset to first page on filter change
    router.push(`/customers?${params.toString()}`);
  };

  const handlePageChange = (newPage: number) => {
    const params = new URLSearchParams(currentSearchParams);
    params.set("page", newPage.toString());
    router.push(`/customers?${params.toString()}`);
  };

  return (Input and html calling those functions)
}

This approach ensures that filtering operates smoothly, and with the page component functioning as a server component, triggering

revalidateTag("customers")
results in automatic data updates.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Javascript: Incorrect comparison result

fs.readFile('./input.txt', (error, data) => { if(error) console.log(error); const input = data.toString(); const dataArray = input.split(/[\n\r ]+/); const lastItem = dataArray.length; let accumulator = 0; let counter = 0; for(le ...

Code snippet for a click event in JavaScript or jQuery

I initially wrote the code in JavaScript, but if someone has a better solution in jQuery, I'm open to it. Here's the scenario: I have multiple questions with corresponding answers. I want to be able to click on a question and have its answer dis ...

How can I route from a Java backend-generated URL to the reset-password page in Next.js?

Here is the link generated by my Java backend for the user to reset their password: http://127.0.0.1:8080/account/reset/finish?key=vljKlxjYh6Cd2xp119bQ. After clicking on this link, the user will be directed to the reset-password page. The API endpoint for ...

Using several v-for loops within a single Vue component file

I am attempting to populate a table with data fetched from my API using three for loops. const events = await app.$axios.get('/api/events/') const markets = await app.$axios.get('/api/markets/') const runners = await app.$axios.g ...

The error "TypeError: b.toLowerCase is not a function in the bootstrap typeahead plugin" indicates that

Currently, I am working on implementing autocomplete search using the typeahead plugin version 3.1.1. My implementation involves PHP, MySQL, AJAX, and JavaScript/jQuery. While everything works perfectly with mysqli in terms of displaying suggestions when t ...

Transforming an array into an object by applying filtering, prioritization, and minimizing loops

Consider the following array: const array = [{ typeName: 'welcome', orientation: 'landscape', languageId: 1, value: 'Welcome' }, { typeName: 'welcome', orientation: 'landscape', languageId: 2, value: & ...

Adjusting page size while maintaining background image continuity

On my webpage, there are animations that intentionally change the height of the page. Initially, when the page loads and the height is set to 100%, the background image covers the entire page. However, as the animations run, the page height can exceed 100% ...

The state variable in a React component remains static even after making a fetch request

edit: After some investigation, I discovered that calling console.log immediately does not display the expected results because setState is asynchronous. However, I am puzzled as to why the props I am using do not render correctly. While three divs are ren ...

Accessing data in JSON format from a URL

I'm working on a website that analyzes data from the game Overwatch. There's this link () that, when visited, displays text in JSON format. Is there a way to use JavaScript to read this data and display it nicely within a <p> tag on my si ...

Tips for navigating through elements generated with ng-repeat as I scroll

There is a list generated using ng-repeat along with two buttons for moving up or down. Users have the option to select any item from the list and utilize the buttons to navigate through it. However, when I lower the selected item using the "go down" butt ...

Does combineLatest detach this from an angular service function?

Check out this test service on Stackblitz! It utilizes the combineLatest method inside the constructor to invoke a service method: constructor() { console.log("TEST SERVICE CONSTRUCTED") this.setParameters.bind(this) this.assignFixedParamete ...

Linking promises together ensures that they always resolve with a result

My understanding of how to chain promises is still not completely solid. What I am trying to achieve is as follows: I have a promise that can either resolve or reject. The following code checks for this and continues if the promise resolves, or stops if i ...

The Ajax call was successful but the callback function failed to return

I've been encountering some challenges with a small application I'm developing. After successfully setting it up to automatically populate one field with the same value entered in another field, I decided to integrate an AJAX request into the scr ...

Unable to retrieve value 'includes' from null object

Currently, I am utilizing Vue.js along with JavaScript. In my code, there is an array of objects named products, each containing a special property called smallest_unit_barcode. My goal is to filter out only those products that have a barcode similar to a ...

What level of security can be expected from this particular JavaScript code when executed on a server environment like nodeJS?

Wondering about potential challenges that may arise when running this code on the server, as well as any alternatives to using eval. var obj = {key1: 'value1', key2: 'value2', key3: 'value3', key4: ['a', 'b&apo ...

Loop through each div using jQuery and dynamically add or remove a class to

I am looking to create an infinite loop that adds a class to each div with a timeout in between. Currently, I have implemented it like this: $(document).ready(function() { $('.small-bordered-box').each(function(i) { var $t = $(this); ...

What causes certain webpack / Babel ES6 imports without a specified extension to resolve as "undefined"?

When I try to import certain ES6 files (such as .js, .jsx, .ts, .tsx) using the syntax import ComponentName from './folder/ComponentName'; (without extension), they end up resolving as undefined. This occurs even though there are no errors from W ...

Triggering a page popup upon a successful ajax response

Currently working on a project and encountering some issues with window.open. Unfortunately, it seems that window.open is not functioning properly within my schema. Any assistance regarding this matter would be greatly appreciated. swal({ title: "Subm ...

Comparing Two Items between Two Arrays

I have a scenario where I need to compare values in two arrays by running a condition. The data is pulled from a spreadsheet, and if the condition is met, I want to copy the respective data. For instance, I aim to compare two cells within a spreadsheet - ...

What is the best way to link multiple return statements in Node.js using ioredis?

Currently utilizing ioredis and interested in retrieving the path and value shown in the code snippet below up to the anonymous function. console.log( function (jsonGraphArg) { return Redis.hget(jsonGraphArg[0], jsonGraphArg[1], function(error ...