> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reshape sources

> Learn how to reshape your Autocomplete sources to create richer search experiences.

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

<Tip>
  Autocomplete is also available as an experimental widget in InstantSearch,
  making it easier to integrate into your search experience.
  For more information,
  see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or
  [React InstantSearch](/doc/api-reference/widgets/autocomplete/react).
</Tip>

When you're browsing a website that fetches content from a database,
the UI isn't fully representative of how that data is structured on the backend.
This allows more human-friendly experiences and interactions.
**A search UI doesn't have to be a one-to-one mapping with your search engine** either.

Autocomplete lets you transform [static](/doc/ui-libraries/autocomplete/core-concepts/sources#static-sources),
[dynamic](/doc/ui-libraries/autocomplete/core-concepts/sources#dynamic-sources),
and [asynchronous sources](/doc/ui-libraries/autocomplete/core-concepts/sources#asynchronous-sources) into a friendlier search UI.

Here are some examples of what you can do with the Reshape API:

* **Apply a limit** of items for a group of sources
* **Remove duplicates** in a group of sources
* **Group** sources by an attribute
* **Sort** sources

In this guide, you'll learn how to use multiple reshape functions to remove duplicates and create a shared limit for your autocomplete suggestions.

## Create a reshape function

To remove duplicates between sources, you can start by creating a `uniqBy` function.
You can later apply this function to your [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches)
and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) sources.

```js JavaScript icon=code theme={"system"}
function uniqBy(predicate) {
  return function runUniqBy(...rawSources) {
    const sources = rawSources.flat().filter(Boolean);
    const seen = new Set();

    return sources.map((source) => {
      const items = source.getItems().filter((item) => {
        const appliedItem = predicate({ source, item });
        const hasSeen = seen.has(appliedItem);

        seen.add(appliedItem);

        return !hasSeen;
      });

      return {
        ...source,
        getItems() {
          return items;
        },
      };
    });
  };
}
```

Autocomplete supports [conditional sources](/doc/ui-libraries/autocomplete/guides/implementing-multiple-search-states#implement-the-empty-query-state): sources sometimes exist and sometimes don't.
This can happen when you display a source on an empty  but not when users start typing
(for example, with a popular <SearchQuery />).
Your function can support this by removing non-existent sources with `filter(Boolean)`.

## The reshape function

Now that you've created the `uniqBy` function,
you can specify the implementation for your sources and use it in the
[`reshape`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-reshape) option:

```js JavaScript icon=code theme={"system"}
const removeDuplicates = uniqBy(({ source, item }) =>
  source.sourceId === "querySuggestionsPlugin" ? item.query : item.label,
);

autocomplete({
  container: "#autocomplete",
  plugins: [recentSearchesPlugin, querySuggestionsPlugin],
  reshape({ sourcesBySourceId }) {
    const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } =
      sourcesBySourceId;

    return [
      removeDuplicates(recentSearchesPlugin, querySuggestionsPlugin),
      Object.values(rest),
    ];
  },
});
```

The reshape option provides three arguments:

* `sources`: the resolved sources provided by [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources)
* `sourcesBySourceId`: the resolved sources grouped by [`sourceId`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-source-id)
* `state`: the Autocomplete [state](/doc/ui-libraries/autocomplete/core-concepts/state)

The `uniqBy` function uses `item.query` as identifier for the [Query Suggestions plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions)
and `item.label` for the [recent searches plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches).
These are the shape of the items that the plugins return.
If you use custom sources, you can use a switch statement based on `source.sourceId`.

Sources are retrieved from `sourcesBySourceId` with their `sourceId` using [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring).
You can return sources that you didn't reshape with `Object.values(rest)`.

## Combine reshape functions

You can create a function that combines results from two different sources.
Use it with `uniqBy` to ensure there are no duplicates and there's a fixed number of combined items.

<video controls>
  <source src="https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/combine-reshape-functions.webm?fit=max&auto=format&n=Gja8rtqcY6eJARlr&q=85&s=8134a86fefc074a9c475166788f65972" type="video/webm" data-path="images/ui-libraries/autocomplete/guides/combine-reshape-functions.webm" />

  <source src="https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/combine-reshape-functions.mp4?fit=max&auto=format&n=Gja8rtqcY6eJARlr&q=85&s=c05aace737f3ea825f52b2f8ffcf167b" type="video/mp4" data-path="images/ui-libraries/autocomplete/guides/combine-reshape-functions.mp4" />
</video>

In the video, four results are always displayed and the number of Query Suggestions varies depending on the number of recent searches.

Here's a `limit` function:

```js JavaScript icon=code theme={"system"}
function limit(value) {
  return function runLimit(...rawSources) {
    const sources = rawSources.flat().filter(Boolean);
    const limitPerSource = Math.ceil(value / sources.length);
    let sharedLimitRemaining = value;

    return sources.map((source, index) => {
      const isLastSource = index === sources.length - 1;
      const sourceLimit = isLastSource
        ? sharedLimitRemaining
        : Math.min(limitPerSource, sharedLimitRemaining);
      const items = source.getItems().slice(0, sourceLimit);
      sharedLimitRemaining = Math.max(sharedLimitRemaining - items.length, 0);

      return {
        ...source,
        getItems() {
          return items;
        },
      };
    });
  };
}
```

Then combine the `uniqBy` and `limit` functions:

```js JavaScript icon=code theme={"system"}
const removeDuplicates = uniqBy(({ source, item }) =>
  source.sourceId === "querySuggestionsPlugin" ? item.query : item.label,
);
const limitSuggestions = limit(4);

autocomplete({
  container: "#autocomplete",
  plugins: [recentSearchesPlugin, querySuggestionsPlugin],
  reshape({ sourcesBySourceId }) {
    const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } =
      sourcesBySourceId;

    return [
      limitSuggestions(
        removeDuplicates(recentSearchesPlugin, querySuggestionsPlugin),
      ),
      Object.values(rest),
    ];
  },
});
```

## Pipe reshape functions

Nested function calls can become cumbersome,
so you can use functional libraries like [Ramda](https://ramdajs.com) to pipe reshape functions instead.

```js JavaScript icon=code theme={"system"}
import { pipe } from "ramda";

const combineSuggestions = pipe(
  uniqBy(({ source, item }) =>
    source.sourceId === "querySuggestionsPlugin" ? item.query : item.label,
  ),
  limit(4),
);

autocomplete({
  container: "#autocomplete",
  plugins: [recentSearchesPlugin, querySuggestionsPlugin],
  reshape({ sourcesBySourceId }) {
    const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } =
      sourcesBySourceId;

    return [
      combineSuggestions(recentSearchesPlugin, querySuggestionsPlugin),
      Object.values(rest),
    ];
  },
});
```

<Note>
  For your reshape functions to support `pipe`,
  you need to make sure your sources are one level deep,
  for example using [`Array.flat`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/flat).
</Note>
