Documentation
Advanced Usage
Current Change Event

Current Change Event

Basic usage

By default Trrack keeps a pointer the latest added node. This can be accessed using trrack.current. Using undo, redo, and todo will update this pointer to the appropriate node. Trrack emits a currentChange event whenever current node changes due to any reason.

To respond to the currentChange event you can register a listener. The currentChange listener gets an optional argument which is the trigger for the current change.

import { initializeTrrack } from '@trrack/core';
 
const trrack = initializeTrrack({ ... });
 
trrack.currentChange(() => {
  // Do something...
});

Cleaning up

currentChange function returns an unsubscribe function which can be used to remove the listener to avoid memory leaks.

import { initializeTrrack } from '@trrack/core';
 
const trrack = initializeTrrack({ ... });
 
const unsubscribe = trrack.currentChange(() => {
  // Do something...
});
 
// Call unsubscribe to remove the listener
unsubscribe();

Advanced usage with state management libraries

Current change event is the primary way to interact with the store from a state management library like redux or pinia.

currentChange event is used to sync the state of the store with current state from Trrack. A general workflow for such integration looks like following:

  • Initialize store
  • Initialize trrack with initialState (possibly from store)
  • Setup store update actions to also inform Trrack about the udpates
  • Register currentChange listener to update the store state with the current state from Trrack in case of traversal.

Let's assume we have a provenance graph which looks as follows:

|
A (State 1)
|
B (State 2)
|
C (State 3) <-- Current
|

The state of the store is the same as State 3.

If we execute trrack.undo() at this point, the graph will be updated:

|
A (State 1)
|
B (State 2) <-- Current
|
C (State 3)
|

However, our store state is still State 3. Inside the currentChange listener we can update the store state with the current state from Trrack.

trrack.currentChange(() => {
  const newState = trrack.getState(); // gets the state from current node
 
  store.update(newState); // update the store state
});

This works great when we are traversing the graph. However, if we execute trrack.apply() when current is pointing to State 3 the store updates to State 4. Our state update actions also inform trrack about the new state, and the graph will be updated:

|
A (State 1)
|
B (State 2)
|
C (State 3)
|
D (State 4) <-- Current
|

Since a new node was added to Trrack and current pointer changed to point to it, the currentChange event is triggered, which updates the store, and may cause re-rendering.

Trrack passes trigger argument to the currentChange listener. If trigger is new we can skip the store update. The value for trigger can be traversal or new. traversal means that the current node was changed due to a undo, redo, or to command. new means that the current node was changed due to a apply command. This way we can avoid updating the store state when the current node is changed due to addition of new node.

trrack.currentChange((trigger: Trigger) => {
  if (trigger === 'new') return; // skip store update
 
  const newState = trrack.getState(); // gets the state from current node
  store.update(newState); // update the store state
});

Such a use case can be found very frequently when two state containers update each other. To handle such case, the currentChange function accepts a second optional boolean argument which is skipOnNew. Passing true to skipOnNew will skip the store update when the current node is changed due to addition of new node.

trrack.currentChange((trigger: Trigger) => {
  /** do not need this anymore */
  // if (trigger === 'new') return;
 
  const newState = trrack.getState(); // gets the state from current node
  store.update(newState); // update the store state
}, true); // skip store update when current node is changed due to addition of new node