enuchi / React-Google-Apps-Script

This is your boilerplate project for developing React apps inside Google Sheets, Docs, Forms and Slides projects. It's perfect for personal projects and for publishing complex add-ons in the Google Workspace Marketplace.
MIT License
1.32k stars 171 forks source link

Is it possible to pass initial state to React root component? #209

Closed seeker1983 closed 6 months ago

seeker1983 commented 6 months ago

I'm trying to setup my dialog so it pulls up some state before rendering and running the first server function to increase loading performance and skip the extra 2-3 seconds for the first call with data loading.

The example here works this way:

const SheetEditor = () => {
  const [names, setNames] = useState([]);

  useEffect(() => {
    // Call a server global function here and handle the response with .then() and .catch()
    serverFunctions.getSheetsData().then(setNames).catch(alert);
  }, []);

and data is only shown after serverFunctions works out.

What I want to setup is something like

const SheetEditor = () => {
const [names, setNames] = useState( INITIAL_STATE);

and then create INITIAL_STATE something like 

const htmlOutput = HtmlService.createHtmlOutputFromFile('dialog-demo').append(
  `<script> 
    const INITIAL_STATE = ${JSON.stringify(getSheetsData())}; 
  </script>`
);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, "Title");

so I can skip the initial call and make loading several seconds faster.

Apparently, the example I provided doesn't work because INITIAL_STATE from htmlOutput is in different context (iframe) than react dom.

Is it possible to somehow setup the passing variable to react state initially (on the dialog open)?

enuchi commented 6 months ago

Hey @seeker1983, try looking into scriptlets: https://developers.google.com/apps-script/guides/html/templates#code_in_scriptlets

Not sure exact syntax that will work but maybe try putting something like the below code in the index.html file:

<body>
   <? window.INITIAL_STATE = getSheetsData(); ?>
</body>

If you can set a value like this onto the global window variable you could access it from the react app like window.INITIAL_STATE.

seeker1983 commented 6 months ago

If anyone would need this at some point, was able to implement it using following workaround

Front-end: (SheetEditor.jsx)

  useEffect(() => {
    if(window.GAS_INPUT) { // Deployed environment
      setNames(setNames);
    } else { // Local development
      window.addEventListener('message', function(event) {
        if (event.data && event.data.type === "setInput" && loading) {
          setNames(event.data.obj);
        }
      }, false);
      window.parent.postMessage('loadInput', '*');
    }
  }, []);

Back-end: (ui.js)


export const openDialog = () => {
  const html = HtmlService.createHtmlOutputFromFile('dialog-demo').append(
        `<script>
          window.GAS_INPUT = ${JSON.stringify(getSheetsData())}; 

          window.addEventListener('message', function(event) {
            if (event.data === "closeDialog") { // Also might be useful
                google.script.host.close();
            }
            if (event.data === 'loadInput') {
              event.source.postMessage({type: 'setInput', obj: window.GAS_INPUT}, '*');
            }
          }, false);
        </script>`)
    .setWidth(600)
    .setHeight(600);
  SpreadsheetApp.getUi().showModalDialog(html, 'Sheet Editor');
};

It works both in local development and when deployed in production

seeker1983 commented 6 months ago

Closing.