How to Access EditorState from Decorated Component
Asked Answered
W

4

8

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?

Wifehood answered 1/10, 2018 at 13:52 Comment(1)
Please share the code where you need clarification or resolution. Theoreticall explanation isn’t enough.Sim
S
3

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

Selfinterest answered 3/10, 2018 at 16:29 Comment(2)
This helped greatly. Thank you for pointing that out. I glossed over that example thinking it was somehow LaTex specific. I'll try to put together a code pen with a simplified example. It is a bit more complex than necessary to specifically illustrate defining callbacks on the blockRenderFn.Wifehood
@BlaineGarrett Hey, have you found a solution? I'm also struggling with a similar problem. I'm currently using context to solve it, but it feels weird, so a way of sharing props or editorState would be proper I suppose.Raleigh
H
2

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!

Hectograph answered 6/7, 2020 at 4:3 Comment(0)
J
2

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.

Jarrell answered 28/12, 2020 at 19:49 Comment(0)
H
1

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 });
Hodeida answered 19/8, 2021 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.