# Adapter Pattern

Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a bridge between two incompatible interfaces by wrapping an instance of one class into an adapter class that presents the expected interface to clients. For more in-depth analysis, visit [Adapter](https://refactoring.guru/design-patterns/adapter).

In frontend development, this pattern is particularly useful when:

* Integrating with third-party libraries or APIs that have different interfaces
* Refactoring legacy code without changing client code
* Transforming data between different parts of the application
* Creating a consistent interface for components that consume data from various sources

## Why Use Adapter Pattern?

**Separation of Concerns**: Keeps data transformation logic separate from presentation logic.

**Reusability**: Adapters can be reused across different components.

**Maintainability**: Changes to data sources only require updating the adapter, not the consuming components.

**Testability**: Adapters can be tested independently from the UI component.

## React Implementation Example

In React applications, adapters are often implemented as higher-order components or custom hooks that transform data before passing it to presentation components:

```
// Simple adapter as a function
function dataAdapter(rawData) {
  return rawData.map(item => ({
    id: item.identifier,
    name: item.displayName,
    value: parseFloat(item.rawValue)
  }));
}

// Using the adapter with a component
function ChartComponent({ rawData }) {
  const adaptedData = dataAdapter(rawData);
  return <Chart data={adaptedData} />;
}
```

## Real World Example — Map Widget Adapter

The application that we were working on has:

1. Various data sources (telemetry, devices, attributes)
2. A `MapWidget` component that expects data in a specific format
3. Need to transform data from multiple sources into a unified structure

```
export function MapWidgetAdapter({
  data,
  dataSources,
  render,
}: MapChartWidgetAdapterProps) {
  const formattedData = useMemo(
    () =>
      (dataSources ?? []).map((dataSource, i) => ({
        dataSource,
        data: transformTelemetryFields(
          mapWidgetData(
            dataSource,
            data?.telemetry?.[i]?.data?.payload,
            data?.devices?.[i]?.data,
            data?.attributes?.[i]?.data?.payload,
          ),
        ),
      })),
    [data?.attributes, dataSources, data?.devices, data?.telemetry],
  );
  
  return render({
    data: formattedData,
  });
}
```

### Key Aspects of this Adapter

**Data Transformation**: The adapter uses helper functions like `transformTelemetryFields` and `mapWidgetData` to convert raw data into the required format.

**Performance Optimization**: Uses `useMemo` to prevent unnecessary re-computation of the transformed data.

**Render Props Pattern**: Uses a render prop to pass the transformed data to the component, allowing for flexibility in how the data is rendered.

### Helper Functions

The adapter uses specialized helper functions to handle specific transformation needs:

```
// Extract the first value from telemetry fields or use default
function getFirstTelemetryValueOrDefault(field: WidgetDataField) {
  if (field.telemetryField == null || !Array.isArray(field.value)) {
    return field.value;
  }
  
  return field.value.length > 0 ? field.value[0] : null;
}

// Transform telemetry fields in all items
export function transformTelemetryFields(items: WidgetDataItem[]) {
  return items.map((item) => ({
    ...item,
    fields: item.fields.map((field) => {
      if (
        field.telemetryField != null &&
        Array.isArray(field.value) &&
        field.value.length > 1
      ) {
        console.warn("Multiple values for telemetry field", field);
      }
      
      return {
        ...field,
        value: getFirstTelemetryValueOrDefault(field),
      };
    }),
  }));
}
```

## Using the Adapter in a Container Component

The `MapWidgetContainer` component shows how to use the adapter in practice:

```
export function MapWidgetContainer({
  widgetId,
}: Readonly<MapWidgeContainerProps>) {
  // Get widget configuration and necessary data
  const widget = useAppSelector((state) => selectWidgetById(state, widgetId));
  const { lastDashboardView } = useGetLastDashboardView();
  const configuration = useWidgetConfiguration<...>(widget);
  
  // Get data from different sources
  const dataSources = configuration?.dataSource === undefined ? [] : configuration.dataSource;
  const telemetry = useMapTelemetry(configuration, lastDashboardView);
  const devices = useMapDevices(dataSources, lastDashboardView);
  const attributes = useMapAttributes(dataSources);
  
  // Calculate loading states
  const isLoading = /* ... */;
  const isFetching = /* ... */;
  
  // Use the adapter to transform and provide data to MapWidget
  return (
    <MapWidgetAdapter
      data={{
        telemetry,
        devices,
        attributes,
      }}
      dataSources={dataSources}
      render={({ data }) => {
        return (
          <MapWidget
            configuration={configuration}
            data={data}
            isLoading={isLoading}
            isFetching={isFetching}
          />
        );
      }}
    />
  );
}
```

This container component:

1. Fetches data from multiple sources
2. Passes the raw data to the adapter
3. Uses the adapter's transformed data to render the `MapWidget`

## Variation of Adapter Pattern in React World

### High-Order Component (HOC) Adapter

```
function withDataAdapter(WrappedComponent) {
  return function AdapterHOC(props) {
    const adaptedData = transformData(props.rawData);
    return <WrappedComponent {...props} data={adaptedData} />;
  };
}

const EnhancedChart = withDataAdapter(Chart);
```

### Custom Hook Adapter

```
function useDataAdapter(rawData) {
  const adaptedData = useMemo(() => {
    return transformData(rawData);
  }, [rawData]);
  
  return adaptedData;
}

function ChartComponent({ rawData }) {
  const adaptedData = useDataAdapter(rawData);
  return <Chart data={adaptedData} />;
}
```

### Function Component Adapter

```
function DataAdapter({ children, data }) {
  const adaptedData = transformData(data);
  return children(adaptedData);
}

<DataAdapter data={rawData}>
  {(adaptedData) => <Chart data={adaptedData} />}
</DataAdapter>
```

## Best Practices for Adapters

**Keep Adapters Simple**: An adapter should only transform data, not contain business logic

**Single Responsibility**: Each adapter should adapt for a single type of component or data structure

**Memoize Results**: Use `useMemo` to prevent unnecessary transformations

**Error Handling**: Include proper error handling in your adapters

**Logging**: Consider adding logging to debug complex transformations

**Testing**: Write unit tests for your adapters to ensure they transform data correctly

## When to Avoid Adapter Pattern

You should avoid Adapter Pattern when:

* Your component already accepts the exact data format provided by your data source.
* The transformation is extremely simple (e.g., a single property rename).
* You can modify both the data source and consumer components to use a common interface.

The Adapter pattern is a powerful tool in frontend development for managing data transformations between incompatible interfaces. As seen in the real-world example of the `MapWidgetAdapter`, it helps separate the concerns of data fetching and transformation from component rendering, resulting in more maintainable and reusable code.

By implementing adapters, you can shield your UI components from changes in data sources and APIs, allowing your application to evolve more easily over time.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.leapwise.co/frontend-handbook/frontend-handbook/development-practices/adapter-pattern.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
