Blaine Garrett
Blaine Garrett

Reputation: 1376

How to Access EditorState from Decorated Component

I'm starting to work with the decorators in draft-js and I'm able to render my components defined in the CompositeDecorator. The documented behavior works great.

That said, I'm trying to figure out how I can access the editorState from within these components. The contentState is the only useful prop passed in, but so far as I can tell I can't resolve the editorState from the contentState.

What I am primarily attempting to do is be able to edit or delete by interacting with the rendered component itself. i.e. opening a dialog to change the entity data. In the onSave() of the dialogForm, I'd need to push the new editorState, but as of right now, it isn't in scope.

Is there a way to access editorState in scope of a decorator component or is there a smarter solution?

Upvotes: 8

Views: 1800

Answers (4)

annezhou920
annezhou920

Reputation: 11

You can try setting a new decorator: https://draftjs.org/docs/advanced-topics-decorators/#setting-new-decorators

getDecorators = (props) => {
    return compositeDecorators([
      {
        strategy: YourDecoratorStrategy,
        component: YourDecoratorComponent,
        props: {
          // your props here
        },
      },
    ]);
  };
const { editorState } = this.state;
const forcedState = EditorState.set(editorState, { decorator: this.getDecorators(this.props) });
this.setState({ editorState: forcedState });

Upvotes: 1

dehumanizer
dehumanizer

Reputation: 1310

You can use a factory method when creating the EditorState with your decorator, i.e. instead of writing something like this:

const compositeDecorator = new CompositeDecorator([
    {
        strategy,
        component: DecoratedComponent,
    }  
])

class YourSuperEditor {
    state = {
        editorState: createWithContent(initialValue, compositeDecorator),
    }
    ...
    render = () => <Editor ... />
}

you could do this:

const compositeDecorator = getters => new CompositeDecorator([
    {
        strategy,
        component: DecoratedComponent,
        props: ...getters,
    }  
])

class YourSuperEditor {
    state = {
        editorState: createWithContent(
            initialValue,
            compositeDecorator({
                getState: () => this.state,
                getInitialValueFromProps: () => this.props.value,
            })
        ),
    }
    ...
    render = () => <Editor ... />
}

P.S. decorated components will re-render only if the EditorState has changed, so if you want to re-render them based on the state/props outside of EditorState you would need to trigger re-render of the Editor by doing setState(() => ({ editorState: newEditorState })) — it's quite ugly but not much we can do until this issue is resolved.

Upvotes: 2

yasserf
yasserf

Reputation: 391

So I just ran into this issue and resolved it using decorator properties.

{
  strategy: handleSentenceStrategy,
  component: SentenceComponent,
  props: {
    setSentenceFocus,
  },
},

And setSentenceFocus is simply a react callback hook (or static function) on whatever creates the editor.

The main issue is whenever the props change it rerenders the Components (which is correct), so if you pass in the editor state your in performance (and visual rendering) problems.

I got around this by the slightly hacky useRef approach.

  const editorStateRef = useRef<EditorState>(editorState)
  editorStateRef.current = editorState
  const updateSentenceFocus = useCallback((focusedSentence: string) => {
    const editorState = editorStateRef.current
    const newEditorState = doSomething()
    setEditorState(newEditorState)
  }, [])

The main point here is the callback itself never changes, and uses a static ref reference. This makes it way more optimal.

This may (most likely will) cause issues in terms of rendering order if your doing things on keystroke, however it worked fine for my usecases. If your doing something more than just a small tweak I would recommend using the blockRendererFn, gives you ultimate flexibility (with more complexity tradeoff).

The other way round works too (although I don't recommend updating editorState in decorators personally).

{
  strategy: handleSentenceStrategy,
  component: SentenceComponent,
  props: {
    getEditorState,
  },
},
const getEditorState = useCallback((): EditorState => {
      return editorStateRef.current
}, [])

Hope that makes sense!

Upvotes: 2

cojennin
cojennin

Reputation: 71

I don't have an explicit answer (though it seems like a good question!) but I was poking around the DraftJs examples and the TexEditor example seems like it might be useful (if you haven't already consulted it). It uses a custom block, and then passes in props that handle updating the editor state in response to changes in the block component.

Let me know if you figure out a solution, I'd like to know how you approached it in the end.

edit: I know you referenced CompositeDecorator, but wasn't able to find an example of what you describe

Upvotes: 3

Related Questions