# Union

Describe a schema which is a union of multiple schemas. This is useful if you need the polymorphic behavior provided by [schema.Array](https://dataclient.io/rest/api/Array.md) or [Values](https://dataclient.io/rest/api/Values.md) but for non-collection fields.

- `definition`: **required** An object mapping the definition of the nested entities found within the input array
- `schemaAttribute`: **required** The attribute on each entity found that defines what schema, per the definition mapping, to use when normalizing.
  Can be a string or a function. If given a function, accepts the following arguments:
  - `value`: The input value of the entity.
  - `parent`: The parent object of the input array.
  - `key`: The key at which the input array appears on the parent object.

#### Instance Methods

- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Union` constructor. This method tends to be useful for creating circular references in schema.

> **Info: Naming**
>
> `Union` is named after the [set theory concept](https://en.wikipedia.org/wiki/Union_\(set_theory\)) just like [TypeScript Unions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)

## Usage

> **Note**
>
> If your data returns an object that you did not provide a mapping for, the original object will be returned in the result and an entity will not be created.

```typescript title="api/Feed"
import { Entity, RestEndpoint, Union } from '@data-client/rest';

export abstract class FeedItem extends Entity {
  readonly id: number = 0;
  declare readonly type: 'link' | 'post';
}
export class Link extends FeedItem {
  readonly type = 'link' as const;
  readonly url: string = '';
  readonly title: string = '';
}
export class Post extends FeedItem {
  readonly type = 'post' as const;
  readonly content: string = '';
}
export const getFeed = new RestEndpoint({
  path: '/feed',
  schema: [
    new Union(
      {
        link: Link,
        post: Post,
      },
      'type',
    ),
  ],
});
```

```tsx title="FeedList"
import { useSuspense } from '@data-client/react';
import { getFeed, Link, Post } from './api/Feed';

function FeedList() {
  const feedItems = useSuspense(getFeed);
  return (
    <div>
      {feedItems.map(item =>
        item.type === 'link' ? (
          <LinkItem link={item} key={item.pk()} />
        ) : (
          <PostItem post={item} key={item.pk()} />
        ),
      )}
    </div>
  );
}
function LinkItem({ link }: { link: Link }) {
  return <a href={link.url}>{link.title}</a>;
}
function PostItem({ post }: { post: Post }) {
  return <div>{post.content}</div>;
}
render(<FeedList />);
```

### Function schemaAttribute

When the discriminator value doesn't directly match schema keys, use a function to compute which schema to use.

```typescript title="api/Feed"
import { Entity, RestEndpoint, Union } from '@data-client/rest';

export abstract class FeedItem extends Entity {
  readonly id: number = 0;
  declare readonly type: 'link' | 'post';
}
export class Link extends FeedItem {
  readonly type = 'link' as const;
  readonly url: string = '';
  readonly title: string = '';
}
export class Post extends FeedItem {
  readonly type = 'post' as const;
  readonly content: string = '';
}
export const getFeed = new RestEndpoint({
  path: '/feed',
  schema: [
    new Union(
      {
        links: Link,
        posts: Post,
      },
      (input: Link | Post, parent: unknown, key: string) => `${input.type}s`,
    ),
  ],
});
```

```tsx title="FeedList"
import { useSuspense } from '@data-client/react';
import { getFeed, Link, Post } from './api/Feed';

function FeedList() {
  const feedItems = useSuspense(getFeed);
  return (
    <div>
      {feedItems.map(item =>
        item.type === 'link' ? (
          <LinkItem link={item} key={item.pk()} />
        ) : (
          <PostItem post={item} key={item.pk()} />
        ),
      )}
    </div>
  );
}
function LinkItem({ link }: { link: Link }) {
  return <a href={link.url}>{link.title}</a>;
}
function PostItem({ post }: { post: Post }) {
  return <div>{post.content}</div>;
}
render(<FeedList />);
```

### Github Events

Contribution activity comes from grouping github events by their type. Each type of Event has its
own distinct schema, which is why we use `Union`

Example app: [github-app](https://github.com/reactive/data-client/tree/master/examples/github-app) ([`src/pages/ProfileDetail/UserEvents.tsx`](https://github.com/reactive/data-client/blob/master/examples/github-app/src/pages/ProfileDetail/UserEvents.tsx), [`src/resources/Event.tsx`](https://github.com/reactive/data-client/blob/master/examples/github-app/src/resources/Event.tsx))
