UI panel with React `useEffect` hook can't call `editDocument`

I’m using React in my UI panel. Contemporary React uses hooks. The concept of the useEffect hook is that,

  • actions like button clicks should set state
  • the useEffect should process ‘side effects’ that you want to happen when it observes state changes.

In my case, I have a UI panel with buttons that allow me to modify the appearance of nodes in my document. Ex. if I click a button that should increase the font size, I click it, increment the state variable fontSize in my react component. Then, useState has fontSize as a dependency which triggers its function to run. Its function calls XD’s editDocument which then has a routine to apply the new text styles to the selection. When I do this though XD gives me an error, “[Error: Panel plugin edits must be initiated from a supported UI event]” (not directly because of this asynchronous paradigm that react has). I guess there’s no way around that?

1 Like

While I’m not entirely sure about this (if someone achieved this, I’ll happily stand corrected), I believe you’re right: There isn’t a clean way around it.

In Panels, editDocument has to get called in the same tick as the user input event (or, in other words, as a direct result of an input event). As far as I understand it, useEffect gets triggered in an asynchronous fashion when the component “detects” it has to re-render. Therefore, the tick of the input event gets lost, making it impossible to use it to apply scenegraph edits.

On another note: It is only after re-reading the React docs after reading your question that I realized I should be using useEffect for a lot of things that I, until now, had handled directly in the input event handlers. Is there any real (noticeable) benefit in using useEffect for side effects (I’ve done this wrong for quite a few also larger-scale personal React projects and haven’t had any problems with it…)

As @pklaschka says, there’s no way around actually doing the editDocument() work in your actual event handler.

But I think that’s a cleaner approach anyway, rather than doing it indirectly.

1 Like

Thanks for confirming the React/XD question. Regarding the React question about useEffect, I haven’t profiled the performance differences myself so I’m not sure but I know that by adhering to the prescribed way allows you to. 1. have input events store state differences using the useState hook. 2. use the useEffect hook to observe state changes and do stuff whenever state changes (which could change from various inputs so it also centralizes where your change handlers are). Note that you can use multiple useEffect hooks for different concerns in one component. The useState hook is asynchronous so you can’t rely on its value to be changed in the same tick. So for me, the useEffect hook is partly about avoiding race conditions too.

1 Like

Thank you for the confirmation.

1 Like

That said, I have exactly the same need (to “debounce” some sliders before doing expensive document changes), so I may have to come up with some complex method for extending an async editDocument() process.

1 Like

I gave up on debounce/throttle for this reason too. I’d be interested to find out if you come up with a way to do it. I was using a lodash/underscore implementation but I don’t think that is important.

It wouldn’t be too hard, given an async editDocument()-called function. It can just await a promise that only fulfills when some input “settles down.”

Don’t know if I’ll get to it anytime soon, but I’ll certainly post here if I do.

Unfortunately, editDocument() locks the UI until resolved (cf. my comment at Debouncing input events in panels - #7 by pklaschka)…

Interesting.

In my experience, if I sleep or do I/O from an async editDocument()-called function, the UI is updated.