StoreSideEffects

See source code
Table of contents

The side effect manager (aka a "correct state enforcer") is responsible for making sure that the editor's state is always correct. This includes things like: deleting a shape if its parent is deleted; unbinding arrows when their binding target is deleted; etc.

class StoreSideEffects<R extends UnknownRecord> {}

Constructor

Constructs a new instance of the StoreSideEffects class

Parameters

NameDescription

store


Methods

registerAfterChangeHandler()

Register a handler to be called after a record is changed. This is useful for side-effects that would update other records - if you want to modify the record being changed, use StoreSideEffects.registerBeforeChangeHandler instead.

registerAfterChangeHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreAfterChangeHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {
  if (next.props.color === 'red') {
    // there can only be one red shape at a time:
    const otherRedShapes = editor
      .getCurrentPageShapes()
      .filter((s) => s.props.color === 'red' && s.id !== next.id)
    editor.updateShapes(
      otherRedShapes.map((s) => ({
        ...s,
        props: { ...s.props, color: 'blue' },
      }))
    )
  }
})

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreAfterChangeHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerAfterCreateHandler()

Register a handler to be called after a record is created. This is useful for side-effects that would update other records. If you want to modify the record being created use StoreSideEffects.registerBeforeCreateHandler instead.

registerAfterCreateHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreAfterCreateHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
  // Automatically create a shape when a page is created
  editor.createShape<TLTextShape>({
    id: createShapeId(),
    type: 'text',
    props: { richText: toRichText(page.name) },
  })
})

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreAfterCreateHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerAfterDeleteHandler()

Register a handler to be called after a record is deleted. This is useful for side-effects that would update other records - if you want to block the deletion of the record itself, use StoreSideEffects.registerBeforeDeleteHandler instead.

registerAfterDeleteHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreAfterDeleteHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {
  // if the last shape in a frame is deleted, delete the frame too:
  const parentFrame = editor.getShape(shape.parentId)
  if (!parentFrame || parentFrame.type !== 'frame') return

  const siblings = editor.getSortedChildIdsForParent(parentFrame)
  if (siblings.length === 0) {
    editor.deleteShape(parentFrame.id)
  }
})

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreAfterDeleteHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerBeforeChangeHandler()

Register a handler to be called before a record is changed. The handler is given the old and new record - you can return a modified record to apply a different update, or the old record to block the update entirely.

Use this handler only for intercepting updates to the record itself. If you want to update other records in response to a change, use StoreSideEffects.registerAfterChangeHandler instead.

registerBeforeChangeHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreBeforeChangeHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerBeforeChangeHandler(
  'shape',
  (prev, next, source) => {
    if (next.isLocked && !prev.isLocked) {
      // prevent shapes from ever being locked:
      return prev
    }
    // other types of change are allowed
    return next
  }
)

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreBeforeChangeHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerBeforeCreateHandler()

Register a handler to be called before a record of a certain type is created. Return a modified record from the handler to change the record that will be created.

Use this handle only to modify the creation of the record itself. If you want to trigger a side-effect on a different record (for example, moving one shape when another is created), use StoreSideEffects.registerAfterCreateHandler instead.

registerBeforeCreateHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreBeforeCreateHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {
  // only modify shapes created by the user
  if (source !== 'user') return shape

  //by default, arrow shapes have no label. Let's make sure they always have a label.
  if (shape.type === 'arrow') {
    return { ...shape, props: { ...shape.props, text: 'an arrow' } }
  }

  // other shapes get returned unmodified
  return shape
})

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreBeforeCreateHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerBeforeDeleteHandler()

Register a handler to be called before a record is deleted. The handler can return false to prevent the deletion.

Use this handler only for intercepting deletions of the record itself. If you want to do something to other records in response to a deletion, use StoreSideEffects.registerAfterDeleteHandler instead.

registerBeforeDeleteHandler<T extends R['typeName']>(
  typeName: T,
  handler: StoreBeforeDeleteHandler<
    R & {
      typeName: T
    }
  >
): () => void

Example

editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {
  if (shape.props.color === 'red') {
    // prevent red shapes from being deleted
    return false
  }
})

Parameters

NameDescription

typeName

T

The type of record to listen for

handler

StoreBeforeDeleteHandler<
  R & {
    typeName: T
  }
>

The handler to call

Returns

() => void

A callback that removes the handler.


registerOperationCompleteHandler()

Register a handler to be called when a store completes an atomic operation.

registerOperationCompleteHandler(
  handler: StoreOperationCompleteHandler
): () => void

Example

let count = 0

editor.sideEffects.registerOperationCompleteHandler(() => count++)

editor.selectAll()
expect(count).toBe(1)

editor.store.atomic(() => {
  editor.selectNone()
  editor.selectAll()
})

expect(count).toBe(2)

Parameters

NameDescription

handler

The handler to call

Returns

() => void

A callback that removes the handler.


Prev
StoreSchema
Next
TLRemoteSyncError