---
title: Relations
description: Conceptual guide to relations in the Content Manager focussing on the technical decisions taken.
tags:
  - content-manager
  - relations
  - redux-store
---

## Summary

Relations are a term used to describe how two or more entities are connected. Previously in the sidebar of an entity,
in Nov2020 we released a refactor that moved these fields into the main editing flow for a better editor experience
and to improve performance of the CMS application when many relations were used.

<img
  src="/img/content-manager/relations/component-example.png"
  alt="An example of the relations input in the CMS edit view"
/>

_above: An example of the relations input in the CMS edit view_

## Data management in frontend

<img
  src="/img/content-manager/relations/relations-statemanagemen-diagram.png"
  alt="a diagram overview explaining how state management works in relations"
/>

_above: A high-level diagram of how relations state management works_

### Preparing relation fields in the store

When you first open an existing entity, we call the admin API and put the data into the store to pre-populate fields
with existing values. However, its important to know when you have fields with `type === 'relation'` in your schema
that the data you receive will not be an array, but rather an object with the count of how many relations in that
field exist. For example, a section of the response may look like this:

```json
{
  "my_relations": {
    "count": 6
  }
}
```

So without intervention, your inputs would try to append new relations to the `my_relations` object, which would not
work. Instead of this, before calling the redux action `INIT_FORM` we recursively find the paths fields based on the
following conditions:

- The field is a relation
- The field is a component
- The field is a repeatable component
- The field is a dynamic zone

These paths _do not_ take into account index values. So if you have a repeatable component field where the schema looks like:

```json
{
  "repeatable_single_component_relation": {
    "type": "component",
    "repeatable": true,
    "component": "basic.relation"
  }
}
```

and the components looks like:

```json
{
  "basic.relation": {
    "attributes": {
      "id": {
        "type": "integer"
      },
      "categories": {
        "type": "relation",
        "relation": "oneToMany",
        "target": "api::category.category",
        "targetModel": "api::category.category",
        "relationType": "oneToMany"
      },
      "my_name": {
        "type": "string"
      }
    }
  }
}
```

Then the path to the relation field would be `repeatable_single_component_relation.categories`. Even though when
relations are added the path to the field in the redux store would be `repeatable_single_component_relation.0.categories`.

Inside the reducer we reduce the array of `relationalFieldPaths` to an object with the `initialValues` clone as
the base. If there is `modifiedData` in the browser i.e. you've made changes to the entity and saved those changes,
we just replace the first level of the field with the `modifiedData` so the data structure is preserved and we're not
losing the relations we had already loaded in the component. If the first part of the path is highlighted as the
`relationalField` then we simply replace that initial object with an empty array.

However, if the first part of the path is either a repeatable component, a dynamic zone or a regular component then we
recursively find the relation fields and replace the object with an array. This is handled by the `findLeafByPathAndReplace`
utility function. This function in short, takes an end path (in this case the relational field) and a primitive to replace
when it finds the endpath (an empty array in this case). It then recursively reduces the paths to the relational field mapping
through arrays if necessary (in the instance of repeatable components for example) replacing the endpath with the primitive.

When this is done, we have successfully prepared our initial data for usage with relations.

### Handling updates to relation fields

Because we've prepared the fields prior to the component loading, adding & removing relations, it's relatively easy to do so.
When a relation is added, we simply push the new relation to the array of relations. When a relation is removed, we simply
filter out the relation from the array of relations. This is handled inside the reducer actions `CONNECT_RELATION` &
`DISCONNECT_RELATION` respectively.

:::note
Connecting relations adds the item to the end of the list, whilst loading more relations prepends to
the beginning of the list. This is the expected behaviour, to keep the order of the list in the UI in sync with the API response.
:::

The `RelationInput` component takes the field in `modifiedData` as its source of truth. You could therefore consider this to
be the `browserState` and `initialData` to be the `serverState`. When relations are loaded they're added to both the `initialData`
and `modifiedData` objects, but when you connect/disconnect only the `modifiedData` is updated. This is useful when we're preparing
data for the API.

### Cleaning data to be posted to the API

The API to update the entity expects relations to be categorised into two groups, a `connect` array and `disconnect` array.
You could do this as the user interacts with the input but we found this to be confusing and then involved us managing three
different arrays which makes the code more complex. Instead, because the browser doesn't really care about whats new and removed
and we have a copy of the slice of data we're mutating from the server we can run a small diff algorithm to determine which
relations have been connected and which have been disconnected. Returning an object like so:

```json
{
  "my_relations": {
    "connect": [{ "id": 1 }, { "id": 2 }],
    "disconnect": []
  }
}
```

## Frontend component architecture

The input field for relation fields consist of two components:

### `RelationInputDataManager`

This container component handles data fetching and data normalization for the `RelationInput` component. This has been extracted from
the `RelationInput` so that Strapi is able to move the underlying component into the design-system if the community would need it
(most other input components can be consumed from there).

### `RelationInput`

This component is the presentational counterpart to the `RelationInputDataManager` component. It renders an input field based on the data passed from the data manager.

Under the hood it is using `react-window` to render a list of relations in a virtualized view. Some fields need to render thousands of relations, which
would otherwise have a negative impact on the overall performance of the content-manager.

## useRelation() hook

This hook takes care of data-fetching and normalizes results relations aswell as search-results.

```ts
const { relations: RelationResults, search: RelationResults, searchFor } = useRelation(reactQueryCacheKey: Array<string | object>, options: Options);
```

### `Options`

`option`s is a mandatory configuration and should implement the following shape:

```ts
type Options = {
  name: string; // name of the relation field
  relation: RelationConfiguration;
  search: SearchConfiguration;
}

type RelationConfiguration = {
  endpoint: string;   // URL from where existing relations should be fetched
  enabled: boolean;   // defines whether relations should be fetched once the hook is called
  pageParams: object; // additional query params which will be appended to `endpoint`
  onLoad: (results: RelationResult[]) => void;  // callback that will be fired after relations have been fetched (paginated)
  normalizeArguments = {
    mainFieldName: string;  // name of the target model main field, determining which field to display (fallback: id)
    shouldAddLink: boolean; // if the user is allowed to read the target model, the returned relations should include a link to the target
    targetModel: object;    // target content-type model
  };
  pageGoal: number; // the current page-count of the already loaded relations used to keep the redux store and query cache in sync.
}

type SearchConfiguration = {
  endpoint: string;   // URL from where new relations should be fetched
  pageParams: object; // additional query params which will be appended to `endpoint`
}
```

### Return values

`relations` and `search` both return a consistent relation format:

```ts
type RelationResults = RelationResult[];

type RelationResult = {
  id: number;
  href?: string; // based on `shouldAddLink` and the `targetModel`
  publicationState: 'draft' | 'published';
  mainField: string; // will fallback to "id" if not set
};
```

#### `relations`

`relations` refers to a [inifinite-query return type](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) from react-query. It exposes paginated relational data
aswell as methods to check if there are more pages or fetch more paginated results. Relations for a given field are fetched as soon as the hook is called.

#### `search`

`search` refers to a [inifinite-query return type](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) from react-query. It exposes paginated search results
for a relational field. Search results are only fetched after `searchFor()` has been called.

#### `searchFor(string)`

`searchFor` is a method which can be used to search for entities which haven't been connected with the source entity yet. The method accepts a search-term: `searchFor("term")`.
