Custom State

Custom state definitions and methods for nodes

If the built-in StateDefinitions that come with DocNode (string, boolean, and number) are enough for you, you can skip this page. But if you need other types (e.g., Date, Set, Customer, etc.), or want to use different methods than the default get, set, and getPrev, this page is for you.

Custom State Definitions

You can create your own custom State Definitions. As an example, the following code shows how the three primitive State Definitions are implemented:

import { defineState } from "docnode";

export const string = (defaultValue: string) =>
  defineState({
    fromJSON: (json) => (typeof json === "string" ? json : defaultValue),
  });

export const number = (defaultValue: number) =>
  defineState({
    fromJSON: (json) => (typeof json === "number" ? json : defaultValue),
  });

// We serialize booleans as numbers (0 or 1) because it's more compact in JSON.
export const boolean = (defaultValue: boolean) =>
  defineState({
    fromJSON: (json) => (json === 0 ? false : json === 1 ? true : defaultValue),
    toJSON: (value) => (!value ? 0 : 1),
  });

Like defineNode, defineState is an identity function (it simply returns the same object that is passed to it), and its purpose is to provide convenient type inference.

// This is what TypeScript compiles the function to. See the types in the next tab →
function defineState(definition) {
  return definition;
}

A StateDefinition consists of up to 3 functions (only 1 is required):

  • fromJSON: The only required function. The value it returns when passed undefined will be the default value.
  • toJSON?: This function is required when the return type of fromJSON is not JSON serializable.
  • methods?: We will discuss this function in the next section.

Due to a limitation in TypeScript, the order of the properties in the defineState function's parameter matters. fromJSON must come first, otherwise the inference won't work correctly.

Custom State Methods

The methods property of the StateDefinition allows you to customize the default methods.

const counter = defineState({
  fromJSON: (json) => (typeof json === "number" ? json : 0),
  methods: ({ get, getPrev, set }) => ({
    get,
    getPrev,
    increment(step = 1) {
      set((n) => (n as number) + step);
    },
    decrement(step = 1) {
      set((n) => (n as number) - step);
    },
  }),
});

const Counter = defineNode({
  type: "counter",
  state: { value: counter },
});

const counter = doc.createNode(Counter);
counter.state.value.increment(3);
counter.state.value.decrement();
counter.state.value.get(); // 2