Answered

Update react state inside miro listener doubles with each request

  • 15 May 2021
  • 2 replies
  • 87 views

Badge +1

I'm developing a webapp where I listen to the selected event on any board component and update the state on the react component, however, after each request, the listener is being triggered 2x compared to the last selection event.


The code is as follows:

function myComponent() {
const [counter, updateCounter] = useState(0);

miro.onReady(() => {
miro.addListener("SELECTION_UPDATED", () => {
console.log("selection updated triggered");
updateCounter((counter) => counter + 1);
})
})

return (
<div>{counter}</div>
)
}

 

The counter values is doubling the values like follow: 0, 1, 3, 7, 15, 31…

Can someone help me understand what's going on? :thinking:

icon

Best answer by Max Harper 15 May 2021, 19:15

@Wender Xavier

I’ve encountered some of this weirdness as well. I think I have a fix for you.

A couple of ideas here:

I roughly think this is what’s going on… 
As you have it coded, it would appear that every time React re-renders/mounts your myComponent you’ll be adding a new listener. Note that after the Miro board is fully loaded the miro.onReady() method will immediately call the callback function - therefore, that callback you have, with the miro.addListenter in there, will get called each time the MyComponent is re-rendered. And, as you’re re-rendering the component every time the state changes you’ll add a new listener each time you update count -- so each listener is setting up a another callback function which updates state and adds another listener… so -- I think this is essentially what’s at the  root of the bizarre ‘doubling’. 


Solution:

Add the listener using the useEffect hook (quick tutorial here) with a blank array as the argument, and this will run that code just once when the component is first mounted. 
 

I got your code working with the structure below. At least I think its what you’re going for. 

import React, { useState, useEffect } from "react";
const miro = window.miro;

const MyComponent = () => {
const [counter, updateCounter] = useState(0);
useEffect(() => {
setListener();
}, []);

function setListener() {
miro.addListener("SELECTION_UPDATED", () => {
console.log("selection updated triggered");
updateCounter((counter) => counter + 1);
});
}

return <div>{counter}</div>;
};

export default MyComponent;

 

Video of it working here:

https://www.loom.com/share/e7144a0f60b74bdeab7498cd76a94dc3

Note also, there is miro.removeListener() to remove a listener …  but that seems a wrong fit for what you’re trying to do.

 

miro.onReady()


I advise using miro.onReady()  in index.js to initiate the entire react app only once Miro is ready… what this ensures is that prior to loading your app, that the miro global variable is established and the methods that are to be used will be available when any of your code within your app calls upon those methods. 

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "./index.css";

window.miro.onReady(() => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
});


 

miro = window.miro

I declare miro = window.miro in a <script>miro = window.miro </script> in the bottom of the app’s index.html

And, I also declare miro = window.miro at the top, after the imports, of each component that has miro methods i.e. miro.____ .  I’m not sure what’s “Perfect” or “Correct” - but I find that that technique works… so I go with it. 

 

create-react-app dev server weirdness:

Additionally … I’ve found while developing react-based Miro plugins using create-react-app and its built-in development server, edits made to your code will trigger a refresh of the app in the plugin iframe but the state variables in your react app will remain and all of the prior session listeners that your app registered with Miro will remain, and you’re app, re-rendering from the top level component down, will invariably re-register listeners again with miro - causing there to be extra listeners now running in that session.  

To fix this, one must “X” close the plugin, and reload it from the toolbar, or refresh the Miro board (refresh the browser page).  You won’t need to X-out each time you make any change, UI changes won’t be an issue… but do stay mindful as you work with functionality related to listeners. 



Hopefully this helps.
Max

View original

2 replies

Userlevel 7
Badge +11

@Wender Xavier

I’ve encountered some of this weirdness as well. I think I have a fix for you.

A couple of ideas here:

I roughly think this is what’s going on… 
As you have it coded, it would appear that every time React re-renders/mounts your myComponent you’ll be adding a new listener. Note that after the Miro board is fully loaded the miro.onReady() method will immediately call the callback function - therefore, that callback you have, with the miro.addListenter in there, will get called each time the MyComponent is re-rendered. And, as you’re re-rendering the component every time the state changes you’ll add a new listener each time you update count -- so each listener is setting up a another callback function which updates state and adds another listener… so -- I think this is essentially what’s at the  root of the bizarre ‘doubling’. 


Solution:

Add the listener using the useEffect hook (quick tutorial here) with a blank array as the argument, and this will run that code just once when the component is first mounted. 
 

I got your code working with the structure below. At least I think its what you’re going for. 

import React, { useState, useEffect } from "react";
const miro = window.miro;

const MyComponent = () => {
const [counter, updateCounter] = useState(0);
useEffect(() => {
setListener();
}, []);

function setListener() {
miro.addListener("SELECTION_UPDATED", () => {
console.log("selection updated triggered");
updateCounter((counter) => counter + 1);
});
}

return <div>{counter}</div>;
};

export default MyComponent;

 

Video of it working here:

https://www.loom.com/share/e7144a0f60b74bdeab7498cd76a94dc3

Note also, there is miro.removeListener() to remove a listener …  but that seems a wrong fit for what you’re trying to do.

 

miro.onReady()


I advise using miro.onReady()  in index.js to initiate the entire react app only once Miro is ready… what this ensures is that prior to loading your app, that the miro global variable is established and the methods that are to be used will be available when any of your code within your app calls upon those methods. 

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "./index.css";

window.miro.onReady(() => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
});


 

miro = window.miro

I declare miro = window.miro in a <script>miro = window.miro </script> in the bottom of the app’s index.html

And, I also declare miro = window.miro at the top, after the imports, of each component that has miro methods i.e. miro.____ .  I’m not sure what’s “Perfect” or “Correct” - but I find that that technique works… so I go with it. 

 

create-react-app dev server weirdness:

Additionally … I’ve found while developing react-based Miro plugins using create-react-app and its built-in development server, edits made to your code will trigger a refresh of the app in the plugin iframe but the state variables in your react app will remain and all of the prior session listeners that your app registered with Miro will remain, and you’re app, re-rendering from the top level component down, will invariably re-register listeners again with miro - causing there to be extra listeners now running in that session.  

To fix this, one must “X” close the plugin, and reload it from the toolbar, or refresh the Miro board (refresh the browser page).  You won’t need to X-out each time you make any change, UI changes won’t be an issue… but do stay mindful as you work with functionality related to listeners. 



Hopefully this helps.
Max

Badge +1

Thank you @Max Harper

After your detailed explanation I could see what I was doing wrong.
Managed to get it working now!

Very helpful! Thank you very much!

Reply