How to add an "id" prop to an EditorState object, then serialize it, then de-serializing it?

Hello Everybody :slightly_smiling_face:

In my use case, I need to have more than one editor on a page, so to map these EditorStates objects to their dom EditorViews I added an id prop to each EditorState and the same id to its view.dom div container.

Iā€™d like to save these EditorStates in the Chrome localStorage, and from reading the docs, I guessed toJSON is the right method to get a serializable object, so I did:

const initialState = EditorState.create({
  doc: "editor"
});

const initialStateData = initialState.toJSON();
initialStateData.id = 1;

I actually wanted to add the id to initialState, because from the docs it seemed possible to add an id custom field and then serialize it. The docs:

When custom fields should be serialized, you can pass them in as an object mapping property names to fields

I couldnā€™t make it work, I tried different syntax, but never got a resulting object like:

{doc: 'editor', selection: {ā€¦}, id:1}

So, I got it to work by adding the id after calling initialState.toJSON() without passing to it an argument.

When I retrieve the serialized object from localStorage Iā€™d like to transform it into a proper EditorState object, I mean with all its methods, to pass it to EditorView:

const view = new EditorView({state});

And to do it, I thought to use fromJSON, but again, as with toJSON, I think I donā€™t get the syntax right when passing arguments, and if I call it without arguments, the EditorState I get as a result has lost its id prop. I think I donā€™t understand

static fromJSON(
json: any,
configā ?: EditorStateConfig = {},
fieldsā ?: Object<StateField<any>>
) ā†’ EditorState

as well as

toJSON(fieldsā ?: Object<StateField<any>>) ā†’ any

Probably an example on how to use it would help. I couldnā€™t find any example in the ā€œExamplesā€ part of the docs.

Also Iā€™d like to know if you think I am doing things the right way, or if thereā€™s a better way (for example, I was wondering if I should use StateFieldsā€¦).

Thanks! :slightly_smiling_face:

Are you storing the ID in a state field for which you defined toJSON and fromJSON methods? Your EditorState.create call doesnā€™t seem to include any extensions at all.

No, I am not. How can I do it? Or if you can point me to docs where I can read about itā€¦

Here are the docs for that. Youā€™ll need to add the state field with the ID to your state extensions, and pass it in an object like {id: myField} when deserializing/serializing your state.

Marijn, thanks!

no problem with creating the state field and add it to the state

// create a custom field "id" and assign it the value 1
let id = StateField.define({
  create() { return 1 }
});

// add "id" to extensions when creating the editor state
let state = EditorState.create({
  doc: "editor", 
  extensions: id
});

console.log("state field id:", state.field(id)) // 1

but then I am stuck with serialization :slight_smile:

I tried

const serializedState = state.toJSON({
  fields: {id}
});

console.log("serializedState: ", serializedState); // {doc: 'editor', selection: {ā€¦}}

or

const serializedState = state.toJSON(
  {id}
); // Uncaught TypeError: value.spec.toJSON is not a function

Please read the docs I just linked a little closer, because youā€™re still not defining toJSON/fromJSON in your state field definition.

Sorry Marijn, youā€™re right, I think I got confused by the two toJSON, anyway no excuses :wink:

From the docs I understand I should do this

// create a custom field "id" and assign it the value 1
let id = StateField.define({
  create() { return 1 },
  toJSON() { }
});

But how should I define toJSON? I donā€™t understand it from:

Since your value can be directly serialized, you can just define toJSON: id => id, fromJSON: id => id

Wonderful! The code seems to work now, because I get the id property in the serializable object:

import {EditorState, StateField} from "@codemirror/state";

// create a custom field "id" with a starting value of 1
// also defined toJSON and fromJSON
let id = StateField.define({
  create() { return 1 },
  toJSON: id => id,
  fromJSON: id => id,
});

// add "id" to extensions when creating the editor state
let state = EditorState.create({
  doc: "editor", 
  extensions: id
});
console.log("state field id:", state.field(id)) // 1

const serializableState = state.toJSON({stateId: id});
console.log("serializableState: ", serializableState); // {doc: 'editor', selection: {ā€¦}, stateId: 1}

Changed the above code a bit, to make it better.

  1. create is now an arrow function, so itā€™s consistent with toJSON and fromJSON definitions
  2. I named the state property id instead of stateId, same as the name of the StateField
import {EditorState, StateField} from "@codemirror/state";

// create a custom field "id" with a starting value of 1
// also defined toJSON and fromJSON
let id = StateField.define({
  create: () => 1,
  toJSON: id => id,
  fromJSON: id => id,
});

// add "id" to extensions when creating the editor state
let state = EditorState.create({
  doc: "editor", 
  extensions: id
});
console.log("state field id:", state.field(id)) // 1

const serializableState = state.toJSON({id});
console.log("serializableState: ", serializableState); // {doc: 'editor', selection: {ā€¦}, id: 1}