Blog

Byron Buckley

< Back to Blog

React Query Data Transformations: Using the Select Option

Published September 24, 2023

Briefly explore the different approaches to data transformation in React Query and demonstrate an example of refactoring a image list component to use React Query's "select" option for data transformation.

React Query provides various options for data transformations, allowing developers to manipulate and format data according to their needs. One of the options available is the "select" option. In this post, I will explore the different approaches to data transformation in React Query. I'll demonstrate an example of refactoring a image list component to use React Query's "select" data transformation.

Summary of Other Data Transformation Options

Before diving into the "select" option, let's briefly summarize the other approaches to data transformation in React Query:

  1. On the backend: This approach involves transforming the data on the server-side before it is sent to the frontend. It can be an ideal solution if you have control over the backend and can request the data in the desired format. However, it may not always be possible. In my case the private REST APIs are consumed by different products inside the company.

  2. In the queryFn: The queryFn is the function passed to the useQuery hook. It allows you to modify the data before it is stored in the query cache. This approach provides flexibility in transforming the data to match your requirements. You can manipulate the data within the queryFn function before returning it. However you will not have access to the original structure. Though if you look at the react-query-devtools, you will see the transformed structure.

  3. In the component: React Query also allows you to perform data transformations directly in the component where the data is being used. This approach gives you the freedom to manipulate the data in the rendering phase, using JavaScript array methods or other techniques.

Initial Code Implementation

Image List Component

Image List Component

Initially, when working with React Query, we might have written our code without utilizing the "select" option. Let's take a look at a code snippet that demonstrates my initial implementation inside the component:

// ImageLibrary.tsx
const transformRows: IImageListItem[] = (rows as TypeImage[])?.map((item: TypeImage) => {
  const { data, name, updated_at, ...rest } = item;
  const thumbnail = data.sources.thumbnail ? (
    <div className={styles.wrapText}>
      <img src={data.sources.thumbnail} alt={name} />
    </div>
  ) : null;
  const imageName = <div className={styles.wrapText}>{name}</div>;
  const fileType = data.format?.toUpperCase();
  const dimensions = `${data.dimensions.width} x ${data.dimensions.height}`;
  const imageSizeInBytes = data?.size || 0;
  const imageSize = getImageSize(imageSizeInBytes);
  const uploadDateTime = updated_at ? (
    <DateDisplay dateFormat={DATE_AT_TIME_FORMAT} value={updated_at?.toString()} />
  ) : null;
  return { ...rest, thumbnail, imageName, fileType, imageSize, dimensions, uploadDateTime };
});

In the above code, we receive the data as an array of rows from the parent component and then manipulate it within the component to format it according to our requirements. While this approach works, it can lead to code duplication if we need to perform similar transformations in multiple components.

Each transformed object has the following properties:

  • thumbnail: A JSX element that displays the thumbnail image of the item, if it exists. If the item does not have a thumbnail image, this property is set to null.
  • imageName: A JSX element that displays the name of the item.
  • fileType: A string that represents the format of the item's data, converted to uppercase.
  • dimensions: A string that represents the dimensions of the item's data.
  • imageSize: A string that represents the size of the item's data, converted to a human-readable format.
  • uploadDateTime: A JSX element that displays the date and time when the item was last updated, if it exists. If the item does not have an update time, this property is set to null.

The getImageSize function is used to convert the size of the item's data from bytes to a human-readable format.

Refactoring with the "Select" Option

To avoid code duplication and centralize the data transformation logic, we can refactor our code using the "select" option provided by React Query. The "select" option allows us to define a selector function that will be applied to the query data before it is returned to the component.

Here's an example of how we can implement a "select" option:

imageListTransform() function

imageListTransform() function

In the refactored code, we define a separate imageListTransform function that takes the raw data as input and returns the transformed data. We then pass this function as the select property to the useImageLibrary hook. React Query automatically applies the selector function to the query data before returning it to the component.

By utilizing the select property, we can keep our component code clean and concise, as the data transformation logic is now separated into a separate function. Optionally this can be passed in as a conditionally property based on the consuming component retaining the option to return the original data shape.

Before

Diff showing the previous implementation inside the ImageList component.

Diff showing the previous implementation inside the ImageList component.

After

Now transformRows becomes a much thinner enrichRows that focuses on providing JSX elements with the pre-transformed primitives. Additionally the interface IImageListItem now only contains primitive properties.

// ImageLibrary.tsx
const enrichRows = (rows as IImageListItem[])?.map((item) => {
  const thumbnail = item.thumbnail ? (
    <div className={styles.wrapText}>
      <img src={item.thumbnail} alt={item.imageName} />
    </div>
  ) : null;
  const imageName = <div className={styles.wrapText}>{item.imageName}</div>;
  return { ...item, thumbnail, imageName };
});

// useImageLibrary.types.ts
export interface IImageListItem {
  created_at?: Date;
  archived?: boolean;
  fileType?: string;
  id: string;
  slug: string;
  space_id: string;
  thumbnail: string;
  original: string;
  imageName: string;
  imageSize: string;
  dimensions: string;
  uploadDateTime: string;
}

In conclusion, React Query provides various options for data transformations, and the "select" option is a powerful tool for centralizing and simplifying data manipulation logic. By using the "select" option, we can ensure code reusability, maintainability, and improved readability in our React Query applications.

To learn more about the pros and cons of each approach, visit: https://react-query.tanstack.com/guides/data-transformations