import { Meta, Canvas } from '@storybook/addon-docs/blocks'
import * as RowDragStories from './rowDrag.stories'

<Meta title="pv-grid/Components/Grid/Cell Change Callback" />

# Cell Change Callback

If you've configured a cell as `editable`, then any edits made will be sent to `onCellChange` when the user confirms the change.

```ts
onCellChange: ( payload: GridConfirmPayload<TDataModel extends GridDataRow> ) => void
```

The `GridConfirmPayload` type can be used with up to four optional type options:

```ts
// GridConfirmPayload
type payload = { rowId: any; columnId: any; nextValue: any; prevValue: any }

// GridConfirmPayload<User> - This is the default, where `User` is the inferred type for your grid
type payload = { rowId: string; columnId: any; nextValue: any; prevValue: any }

// GridConfirmPayload<User, 'name'> - Infers types from model
type payload = {
    rowId: string
    columnId: 'name'
    nextValue: string
    prevValue: string
}

// GridConfirmPayload<User, 'name' | 'age'> - Same as previous, but with a union for columns
type payload =
    | { rowId: string; columnId: 'name'; nextValue: string; prevValue: string }
    | { rowId: string; columnId: 'age'; nextValue: number; prevValue: number }

// GridConfirmPayload<User, 'addedDate', Date> - nextValue and prevValue both set to Date
type payload = {
    rowId: string
    columnId: 'addedDate'
    nextValue: Date
    prevValue: Date
}

// GridConfirmPayload<User, 'addedDate', Date, string> - nextValue and prevValue set to different types
type payload = {
    rowId: string
    columnId: 'addedDate'
    nextValue: Date
    prevValue: string
}
```

### Default Typing

Without additional typing information, the `payload` will be of type `GridConfirmPayload<TDataModel>` and the `columnId`, `nextValue` and `prevValue` will all by typed as `any`. `rowId` should match the id type from TDataModel (which should match the type of `rows` in your Grid). For `User`, it would end up looking like this:

```tsx
const onCellChange = React.useCallback((payload: GridConfirmPayload<User>) => {
    if (payload.columnId === 'name') {
        // payload.nextValue is any
    } else if (payload.columnId === 'age') {
        // payload.nextValue is any
    }
}, [])
```

### Easy Typing

If you want to easily bring all your attributes into the handler (telling the compiler that all attributes are editable) you can do that by passing `keyof YourType`:

```tsx
const onCellChange = React.useCallback(
    (payload: GridConfirmPayload<User, keyof User>) => {
        if (payload.columnId === 'name') {
            // payload.nextValue is string
        } else if (payload.columnId === 'age') {
            // payload.nextValue is number
        }
    },
    []
)
```

This will signal to the callback that _any_ of the properties on `User` can be edited which may not be the case (and rarely is with the `id` column). You can use any of the exclusion helpers in TypeScript to limit the list, or you may choose to just pass a union of field names:

```ts
// All three of these are equivalent
type payloadOne = GridConfirmPayload<User, Exclude<keyof User, 'id'>>
type payloadTwo = GridConfirmPayload<User, keyof Omit<User, 'id'>>
type payloadThree = GridConfirmPayload<User, 'age' | 'name'>
```

### Advanced Typing

In cases where your the Grid Cell Editor is going to return a different type than your model, or where your column ids don't map to actual fields on your model, you may need to use this approach.

#### Non-model fields

Even though this example combines model fields with a non-model field, you can use non-model fields by themselves. Regardless of your approach, you must always pass the row type as the first argument, in this case `User`.

- In the following example, `name` and `birthDate` are supported as editable fields
- `name` will infer its original type from `User`
- `birthDate` needs a specific type otherwise it will default to `any`

```tsx
const onCellChange = React.useCallback(
    (
        payload:
            | GridConfirmPayload<User, 'name'>
            | GridConfirmPayload<User, 'birthDate', Date>
    ) => {
        if (payload.columnId === 'name') {
            // payload.nextValue is string
        } else if (payload.columnId === 'birthDate') {
            // payload.nextValue is Date
        }
    },
    []
)
```

#### Different types for `prevValue` and `nextValue`

A good use case for this might be if you are using our GridEditorCombobox component. It will return values as `{label: "Option", value: ... }` and optionally `null` if the Combobox is configured as clearable.

Lets pretend our `age` field is part of a Combobox where the user selects an age range, and we store the `value`. Since our model has `age` as `number`, but our editor will return a richer object, we can use the more advanced types.

In the following example, `name` and `age` are supported as editable fields

- `name` will infer its original type from `User`
- `age` will use the provided types for `nextValue` and `prevValue` (If omitted, `prevValue` will be set to match the type of `nextValue` )

```tsx
const onCellChange = React.useCallback(
    (
        payload:
            | GridConfirmPayload<User, 'name'>
            | GridConfirmPayload<
                  User,
                  'age',
                  { label: string; value: number },
                  number
              >
    ) => {
        if (payload.columnId === 'name') {
            // payload.nextValue is string
        } else if (payload.columnId === 'age') {
            // payload.prevValue is number
            // payload.nextValue.label is string
            // payload.nextValue.value is number
        }
    },
    []
)
```
