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}