🚀 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.
19 |
20 |
21 | ---
22 |
23 | ## 📝 Table of Contents
24 |
25 | - [About](#about)
26 | - [Install](#install)
27 | - [Prerequisites](#prerequisites)
28 | - [Getting started](#getting-started)
29 | - [Deploy](#deploy)
30 | - [Local Development](#local-development)
31 | - [Using React DevTools](#dev-tools)
32 | - [Usage](#usage)
33 | - [The included sample app](#the-included-sample-app)
34 | - [Typescript](#new-typescript)
35 | - [Adding packages](#adding-packages)
36 | - [Styles](#styles)
37 | - [Modifying scopes](#modifying-scopes)
38 | - [Calling server-side Google Apps Script functions](#calling-server-side-google-apps-script-functions)
39 | - [Autocomplete](#Autocomplete)
40 | - [Authors](#authors)
41 | - [Acknowledgments](#acknowledgement)
42 |
43 |
44 |
45 | ## 🔎 About
46 |
47 | [Google Apps Script](https://developers.google.com/apps-script/overview) is Google's Javascript-based development platform for building applications and add-ons for Google Sheets, Docs, Forms and other Google Apps.
48 |
49 | Google Apps Scripts lets you add custom [user interfaces inside dialog windows](https://developers.google.com/apps-script/guides/html). Using this template, it's easy to run [React](https://reactjs.org/) apps inside these dialogs, and build everything from small projects to advanced add-ons that can be published in the Google Workspace Marketplace.
50 |
51 |
52 |
53 |
54 |
55 | This repo is a boilerplate project for developing React apps with Google Apps Script projects. You can use this starter template to build your own React apps and deploy them inside Google Sheets, Docs, Forms and Slides for use in dialogs and sidebars. Sample code is provided showing how your React app can interact with the underlying Google Apps Script server-side code.
56 |
57 | Read on to get started with your own project!
58 |
59 |
60 | ## 🚜 Install
61 |
62 | These instructions will get you set up with a copy of the React project code on your local machine. It will also get you logged in to `clasp`, which lets you manage script projects from the command line.
63 |
64 | See [deploy](#deploy) for notes on how to deploy the project and see it live in a Google Spreadsheet.
65 |
66 | ### Prerequisites
67 |
68 | - Make sure you're running at least [Node.js](https://nodejs.org/en/download/) v18 and [yarn (classic)](https://classic.yarnpkg.com/lang/en/docs/install/).
69 |
70 | - You'll need to enable the Google Apps Script API. You can do that by visiting [script.google.com/home/usersettings](https://script.google.com/home/usersettings).
71 |
72 | - To use live reload while developing, you'll need to serve your files locally using HTTPS. See [local development](#local-development) below for instructions on setting up your local environment.
73 |
74 | ### 🏁 Getting started
75 |
76 | Full steps to getting your local environment set up, deploying your app, and also running your app locally for local development are shown in the video below:
77 |
78 | https://github.com/enuchi/React-Google-Apps-Script/assets/31550519/83622b83-0d0e-43de-a589-36f96d51c9c4
79 |
80 |
81 | **1.** First, let's clone the repo and install the dependencies. This project is published as a public template, so you can also fork the repo or select "Use this template" in GitHub.
82 |
83 | ```bash
84 | git clone https://github.com/enuchi/React-Google-Apps-Script.git
85 | cd React-Google-Apps-Script
86 | yarn install
87 | ```
88 |
89 |
90 | **2.** Next, we'll need to log in to [clasp](https://github.com/google/clasp), which lets us manage our Google Apps Script projects locally.
91 |
92 | ```bash
93 | yarn run login
94 | ```
95 | **3.** Now let's run the setup script to create a New spreadsheet and script project from the command line.
96 |
97 | ```bash
98 | yarn run setup
99 | ```
100 |
101 | Alternatively, you can use an existing Google Spreadsheet and Script file instead of creating a new one.
102 |
103 |
104 | See instructions here for using an existing project.
105 |
106 | You will need to update the `.clasp.json` file in the root of this project with the following three key/value pairs (see .clasp.json.SAMPLE for reference):
107 |
108 | ```json
109 | {
110 | "scriptId": "1PY037hPcy................................................",
111 | "parentId": ["1Df30......................................."],
112 | "rootDir": "./dist"
113 | }
114 | ```
115 |
116 | - `scriptId`: Your existing script project's `scriptId`. You can find it by opening your spreadsheet, selecting **Tools > Script Editor** from the menubar, then **File > Project properties**, and it will be listed as "Script ID".
117 |
118 | - `parentId`: An array with a single string, the ID of the parent file (spreadsheet, doc, etc.) that the script project is bound to. You can get this ID from the url, where the format is usually `https://docs.google.com/spreadsheets/d/{id}/edit`. This allows you to run `npm run open` and open your file directly from the command line.
119 |
120 | - `rootDir`: This should always be `"./dist"`, i.e. the local build folder that is used to store project files.
121 |
122 |
123 |
124 | Next, let's deploy the app so we can see it live in Google Spreadsheets.
125 |
126 | https://github.com/enuchi/React-Google-Apps-Script/assets/31550519/0c67c4b8-e3f5-4345-8460-470e9211aeb9
127 |
128 |
129 |
130 | ## 🚀 Deploy
131 |
132 | Run the deploy command. You may be prompted to update your manifest file. Type 'yes'.
133 |
134 | ```bash
135 | yarn run deploy
136 | ```
137 |
138 | The deploy command will build all necessary files using production settings, including all server code (Google Apps Script code), client code (React bundle), and config files. All bundled files will be outputted to the `dist/` folder, then pushed to the Google Apps Script project.
139 |
140 | Now open Google Sheets and navigate to your new spreadsheet (e.g. the file "My React Project"). You can also run `yarn run open`. Make sure to refresh the page if you already had it open. You will now see a new menu item appear containing your app!
141 |
142 |
143 |
144 | ## 🎈 Local Development
145 |
146 | We can develop our client-side React apps locally, and see our changes directly inside our Google Spreadsheet dialog window.
147 |
148 | There are two steps to getting started: installing a certificate (first time only), and running the start command.
149 |
150 | 1. Generating a certificate for local development
151 |
152 | Install the mkcert package:
153 |
154 | ```bash
155 | # mac:
156 | brew install mkcert
157 |
158 | # windows:
159 | choco install mkcert
160 | ```
161 |
162 | [More install options here.](https://github.com/FiloSottile/mkcert#installation)
163 |
164 | Then run the mkcert install script:
165 |
166 | ```bash
167 | mkcert -install
168 | ```
169 |
170 | Create the certs in your repo:
171 |
172 | ```
173 | yarn run setup:https
174 | ```
175 |
176 | 2. Now you're ready to start:
177 | ```bash
178 | yarn run start
179 | ```
180 |
181 | The start command will create and deploy a development build, and serve your local files.
182 |
183 | After running the start command, navigate to your spreadsheet and open one of the menu items. It should now be serving your local files. When you make and save changes to your React app, your app will reload instantly within the Google Spreadsheet, and have access to any server-side functions!
184 |
185 | https://github.com/enuchi/React-Google-Apps-Script/assets/31550519/981604ac-bdea-489d-97fa-72e6d24ba6dd
186 |
187 |
188 |
189 | ### 🔍 Using React DevTools
190 |
191 | React DevTools is a tool that lets you inspect the React component hierarchies during development.
192 |
193 |
194 | Instructions for installing React DevTools
195 |
196 |
197 |
198 | You will need to use the "standalone" version of React DevTools since our React App is running in an iframe ([more details here](https://github.com/facebook/react/tree/master/packages/react-devtools#usage-with-react-dom)).
199 |
200 | 1. In your repo install the React DevTools package as a dev dependency:
201 |
202 | ```bash
203 | yarn add -D react-devtools
204 | ```
205 |
206 | 2. In a new terminal window run `npx react-devtools` to launch the DevTools standalone app.
207 |
208 | 3. Add `` to the top of your `` in your React app, e.g. in the [index.html](https://github.com/enuchi/React-Google-Apps-Script/blob/e73e51e56e99903885ef8dd5525986f99038d8bf/src/client/dialog-demo-bootstrap/index.html) file in the sample Bootstrap app.
209 |
210 | 4. Deploy your app (`yarn run deploy:dev`) and you should see DevTools tool running and displaying your app hierarchy.
211 |
212 |
213 |
214 | 5. Don't forget to remove the ``.
279 |
280 | If set up properly, this will load packages from the CDN in production and will reduce your overall bundle size.
281 |
282 | Make sure that you update the script tag with the same version of the package you are installing with yarn, so that you are using the same version in development and production.
283 |
284 | ### Styles
285 |
286 | By default this project supports global CSS stylesheets. Make sure to import your stylesheet in your entrypoint file [index.js](./src/client/dialog-demo/index.js):
287 |
288 | ```javascript
289 | import './styles.css';
290 | ```
291 |
292 | Many external component libraries require a css stylesheet in order to work properly. You can import stylesheets in the HTML template, [as shown here with the Bootstrap stylesheet](./src/client/dialog-demo-bootstrap/index.html).
293 |
294 | ### Modifying scopes
295 |
296 | The included app only requires access to Google Spreadsheets and to loading dialog windows. If you make changes to the app's requirements, for instance, if you modify this project to work with Google Forms or Docs, make sure to edit the oauthScopes in the [appscript.json file](./appsscript.json).
297 |
298 | See https://developers.google.com/apps-script/manifest for information on the `appsscript.json` structure.
299 |
300 | ### Calling server-side Google Apps Script functions
301 |
302 | This project uses the [gas-client](https://github.com/enuchi/gas-client) package to more easily call server-side functions using promises.
303 |
304 | ```js
305 | // Google's client-side google.script.run utility requires calling server-side functions like this:
306 | google.script.run
307 | .withSuccessHandler((response) => doSomething(response))
308 | .withFailureHandler((err) => handleError(err))
309 | .addSheet(sheetTitle);
310 |
311 | // Using gas-client we can use more familiar promises style like this:
312 | import { GASClient } from 'gas-client';
313 | const { serverFunctions, scriptHostFunctions } = new GASClient({});
314 |
315 | // We now have access to all our server functions, which return promises!
316 | serverFunctions
317 | .addSheet(sheetTitle)
318 | .then((response) => doSomething(response))
319 | .catch((err) => handleError(err));
320 |
321 | // Or with async/await:
322 | async () => {
323 | try {
324 | const response = await serverFunctions.addSheet(sheetTitle);
325 | doSomething(response);
326 | } catch (err) {
327 | handleError(err);
328 | }
329 | };
330 |
331 | // Use scriptHostFunctions to control dialogs
332 | scriptHostFunctions.close(); // close a dialog or sidebar
333 | scriptHostFunctions.setWidth(400); // set dialog width to 400px
334 | scriptHostFunctions.setHeight(800); // set dialog height to 800px
335 |
336 | ```
337 |
338 | In development, `gas-client` will allow you to call server-side functions from your local environment. In production, it will use Google's underlying `google.script.run` utility.
339 |
340 | ### Autocomplete
341 |
342 | This project includes support for autocompletion and complete type definitions for Google Apps Script methods.
343 |
344 | 
345 |
346 | All available methods from the Google Apps Script API are shown with full definitions and links to the official documentation, plus information on argument, return type and sample code.
347 |
348 |
349 |
350 | ## ✍️ Authors
351 |
352 | - [@enuchi](https://github.com/enuchi) - Creator and maintainer
353 |
354 | See the list of [contributors](https://github.com/enuchi/React-Google-Apps-Script/contributors) who participated in this project.
355 |
356 |
357 |
358 | ## 🎉 Acknowledgements
359 |
360 | Part of this project has been adapted from [apps-script-starter](https://github.com/labnol/apps-script-starter), a great starter project for server-side projects ([license here](https://github.com/labnol/apps-script-starter/blob/master/LICENSE)).
361 |
--------------------------------------------------------------------------------
/appsscript.json:
--------------------------------------------------------------------------------
1 | {
2 | "timeZone": "America/New_York",
3 | "dependencies": {},
4 | "exceptionLogging": "STACKDRIVER",
5 | "oauthScopes": [
6 | "https://www.googleapis.com/auth/script.container.ui",
7 | "https://www.googleapis.com/auth/spreadsheets"
8 | ],
9 | "runtimeVersion": "V8"
10 | }
11 |
--------------------------------------------------------------------------------
/dev/dev-server-wrapper.html:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 | Dev Server
22 |
23 |
24 |
32 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | globalSetup: './test/global-setup.js',
3 | globalTeardown: './test/global-teardown.js',
4 | testEnvironment: './test/puppeteer-environment.js',
5 | reporters: ['default', '/test/utils/image-reporter.js'],
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-google-apps-script",
3 | "version": "3.1.0",
4 | "type": "module",
5 | "description": "Starter project for using React with Google Apps Script",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/enuchi/React-Google-Apps-Script.git"
9 | },
10 | "scripts": {
11 | "dev": "vite",
12 | "lint": "eslint .",
13 | "test:integration": "jest --forceExit test/local-development.test",
14 | "test:integration:extended": "cross-env IS_EXTENDED=true jest --forceExit test/local-development.test",
15 | "test:integration:extended:ci-reporter": "cross-env IS_EXTENDED=true jest --forceExit || node test/utils/image-reporter-standalone.js",
16 | "login": "clasp login",
17 | "setup": "rimraf .clasp.json && mkdirp dist && clasp create --type sheets --title \"My React Project\" --rootDir ./dist && mv ./dist/.clasp.json ./.clasp.json && rimraf dist",
18 | "open": "clasp open --addon",
19 | "push": "clasp push",
20 | "setup:https": "mkdirp certs && mkcert -key-file ./certs/key.pem -cert-file ./certs/cert.pem localhost 127.0.0.1",
21 | "build:dev": "tsc && vite build --mode development",
22 | "build": "tsc && vite build --mode production",
23 | "deploy:dev": "yarn build:dev && yarn push",
24 | "deploy": "yarn build && yarn push",
25 | "start": "yarn deploy:dev && yarn dev"
26 | },
27 | "keywords": [
28 | "react",
29 | "google",
30 | "apps",
31 | "script",
32 | "sheets"
33 | ],
34 | "author": "Elisha Nuchi",
35 | "license": "MIT",
36 | "engines": {
37 | "node": ">=10.0.0",
38 | "npm": ">=6.0.0"
39 | },
40 | "dependencies": {
41 | "@emotion/react": "^11.10.6",
42 | "@emotion/styled": "^11.10.6",
43 | "@mui/material": "^5.11.11",
44 | "gas-client": "^1.2.0",
45 | "prop-types": "^15.8.1",
46 | "react": "^18.2.0",
47 | "react-bootstrap": "^2.4.0",
48 | "react-dom": "^18.2.0",
49 | "react-transition-group": "^4.4.2"
50 | },
51 | "devDependencies": {
52 | "@babel/preset-env": "^7.24.6",
53 | "@google/clasp": "^2.4.2",
54 | "@types/expect-puppeteer": "^5.0.0",
55 | "@types/jest-environment-puppeteer": "^5.0.2",
56 | "@types/node": "^20.11.30",
57 | "@types/puppeteer": "^5.4.6",
58 | "@types/react": "^18.2.66",
59 | "@types/react-dom": "^18.2.22",
60 | "@typescript-eslint/eslint-plugin": "^7.2.0",
61 | "@typescript-eslint/parser": "^7.2.0",
62 | "@vitejs/plugin-react-swc": "^3.5.0",
63 | "autoprefixer": "^10.4.19",
64 | "aws-sdk": "^2.1106.0",
65 | "cross-env": "^7.0.3",
66 | "dotenv": "^16.4.5",
67 | "eslint": "^8.57.0",
68 | "eslint-config-airbnb-base": "^15.0.0",
69 | "eslint-config-prettier": "^8.5.0",
70 | "eslint-plugin-googleappsscript": "^1.0.4",
71 | "eslint-plugin-import": "^2.29.1",
72 | "eslint-plugin-jest": "^26.5.3",
73 | "eslint-plugin-prettier": "^4.0.0",
74 | "eslint-plugin-react-hooks": "^4.6.0",
75 | "eslint-plugin-react-refresh": "^0.4.6",
76 | "gas-types-detailed": "^1.1.2",
77 | "jest": "^28.1.1",
78 | "jest-environment-node": "^28.1.1",
79 | "jest-image-snapshot": "^5.1.0",
80 | "postcss": "^8.4.38",
81 | "postcss-preset-env": "^9.5.4",
82 | "prettier": "^2.7.0",
83 | "puppeteer": "^14.3.0",
84 | "puppeteer-extra": "^3.2.3",
85 | "puppeteer-extra-plugin-stealth": "^2.9.0",
86 | "rollup": "^4.18.0",
87 | "tailwindcss": "^3.4.3",
88 | "typescript": "^5.2.2",
89 | "vite": "^5.2.0",
90 | "vite-plugin-singlefile": "^2.0.1",
91 | "vite-plugin-static-copy": "^1.0.1"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/scripts/generate-cert.ps1:
--------------------------------------------------------------------------------
1 | # reference code: https://stackoverflow.com/questions/70226493/webpack-dev-server-and-https-this-site-can-t-be-reached
2 | # reference issue: https://github.com/FiloSottile/mkcert/issues/286
3 |
4 | $dnsName = "localhost"
5 | $expiry = [DateTime]::Now.AddYears(1);
6 | $repoRoot = Split-Path $PSScriptRoot
7 | $certsDir = "$repoRoot\certs";
8 | $fileName = "cert.pfx";
9 | $passwordText = "abc123";
10 | $name = "ReactApp";
11 |
12 | Write-Host "Creating cert directly into CurrentUser\My store"
13 |
14 | $certificate = New-SelfSignedCertificate `
15 | -KeyExportPolicy 'Exportable' `
16 | -CertStoreLocation Cert:\CurrentUser\My `
17 | -Subject $name `
18 | -FriendlyName $name `
19 | -DnsName $dnsName `
20 | -NotAfter $expiry
21 |
22 | $certFile = Join-Path $certsDir $fileName
23 |
24 | Write-Host "Exporting certificate to $certFile"
25 |
26 | $password = ConvertTo-SecureString `
27 | -String $passwordText `
28 | -Force -AsPlainText
29 |
30 | Export-PfxCertificate `
31 | -Cert $certificate `
32 | -FilePath $certFile `
33 | -Password $password | Out-Null
34 |
35 | Write-Host "Importing $certFile to CurrentUser\Root store for immediate system wide trust"
36 |
37 | Import-PfxCertificate `
38 | -FilePath $certFile `
39 | -CertStoreLocation Cert:\LocalMachine\Root `
40 | -Password $password | Out-Null
--------------------------------------------------------------------------------
/src/client/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "airbnb-base",
5 | "plugin:prettier/recommended",
6 | "eslint:recommended",
7 | "plugin:@typescript-eslint/recommended",
8 | "plugin:react-hooks/recommended"
9 | ],
10 | "plugins": ["react-refresh", "prettier"],
11 | "rules": {
12 | "prettier/prettier": "error",
13 | "camelcase": "warn",
14 | "import/prefer-default-export": "warn",
15 | "import/no-extraneous-dependencies": "warn",
16 | "prefer-object-spread": "warn",
17 | "spaced-comment": "off",
18 | "react-refresh/only-export-components": [
19 | "warn",
20 | { "allowConstantExport": true }
21 | ],
22 | "import/extensions": [
23 | "error",
24 | "ignorePackages",
25 | {
26 | "js": "never",
27 | "jsx": "never",
28 | "ts": "never",
29 | "tsx": "never"
30 | }
31 | ]
32 | },
33 | "settings": {
34 | "react": {
35 | "version": "detect"
36 | },
37 | "import/resolver": {
38 | "node": {
39 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
40 | }
41 | }
42 | },
43 | "env": { "browser": true, "es2020": true },
44 | "parser": "@typescript-eslint/parser"
45 | }
46 |
--------------------------------------------------------------------------------
/src/client/README.md:
--------------------------------------------------------------------------------
1 | # Client (our React code)
2 |
3 | This directory is where we store the source code for our client-side React apps.
4 |
5 | We have multiple directories in here because our app creates menu items that open multiple dialog windows. Each dialog opens a separate app, so each directory here represents its own distinct React app. Our webpack configuration will generate a separate bundle for each React app.
6 |
7 | ## Requirements
8 |
9 | Each React app will need:
10 | - an entrypoint, usually a file named `index.js`, that loads the app
11 | - an HTML file that acts as a template, in which the bundled React app is loaded
12 |
13 | You'll need to declare the following in [webpack.config.js](../../webpack.config.js):
14 | - **name**: just a name to print in the webpack console, e.g. 'CLIENT - Dialog Demo'
15 | - **entry**: the path to the entry point for the app, e.g. './src/client/dialog-demo/index.js'
16 | - **filename**: the name of the html file that is generated. The server code will reference this filename to load the app into a dialog window. E.g. 'dialog-demo'
17 | - **template**: the path to the HTML template for the app, e.g. './src/client/dialog-demo/index.html'
18 |
19 |
20 | ### Adding or removing an entrypoint
21 | Your app or use case may only require a single dialog or sidebar, or you may want to add more than are included in the sample app.
22 |
23 | To edit the entrypoints, you will need to:
24 |
25 | 1. Create or remove the entrypoint directories in the client source code. For instance, you can remove `./src/client/sidebar-about-page` altogether, or copy it and modify the source code. See above [requirements](#requirements).
26 |
27 | 2. Modify the server-side code to load the correct menu items and expose the correct public functions:
28 | - [ui file](../server/ui.js)
29 | - [index file](../server/index.js)
30 |
31 | 3. Modify the `clientEntrypoints` config in the [webpack config file](../../webpack.config.js).
32 |
--------------------------------------------------------------------------------
/src/client/dialog-demo-bootstrap/components/FormInput.tsx:
--------------------------------------------------------------------------------
1 | import { useState, ChangeEvent, FormEvent } from 'react';
2 | import { Form, Button, Col, Row } from 'react-bootstrap';
3 |
4 | interface FormInputProps {
5 | submitNewSheet: (sheetName: string) => {
6 | name: string;
7 | index: number;
8 | isActive: boolean;
9 | };
10 | }
11 |
12 | const FormInput = ({ submitNewSheet }: FormInputProps) => {
13 | const [newSheetName, setNewSheetName] = useState('');
14 |
15 | const handleChange = (event: ChangeEvent) =>
16 | setNewSheetName(event.target.value);
17 |
18 | const handleSubmit = (event: FormEvent) => {
19 | event.preventDefault();
20 | if (newSheetName.length === 0) return;
21 | submitNewSheet(newSheetName);
22 | setNewSheetName('');
23 | };
24 |
25 | return (
26 |
28 | Add a new sheet
29 |
30 |
44 | This is a sample app that uses the react-bootstrap library
45 | to help us build a simple React app. Enter a name for a new sheet, hit
46 | enter and the new sheet will be created. Click the red{' '}
47 | × next to the sheet name to
48 | delete it.
49 |
37 |
38 | ☀️ MUI demo! ☀️
39 |
40 |
41 |
42 | This is a sample app that uses the mui library to help us
43 | build a simple React app. Enter a name for a new sheet, hit enter and
44 | the new sheet will be created. Click the red button next to the sheet
45 | name to delete it.
46 |
47 |
48 |
49 | {names.length > 0 && (
50 | {
52 | return {
53 | sheetName: name.name,
54 | goToButton: (
55 |
62 | ),
63 | deleteButton: (
64 |
71 | ),
72 | };
73 | })}
74 | />
75 | )}
76 |
41 | );
42 | };
43 |
44 | export default SheetButton;
45 |
46 | SheetButton.propTypes = {
47 | sheetDetails: PropTypes.shape({
48 | index: PropTypes.number,
49 | name: PropTypes.string,
50 | isActive: PropTypes.bool,
51 | }),
52 | deleteSheet: PropTypes.func,
53 | setActiveSheet: PropTypes.func,
54 | };
55 |
--------------------------------------------------------------------------------
/src/client/dialog-demo-tailwindcss/components/SheetEditor.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { TransitionGroup, CSSTransition } from 'react-transition-group';
3 | import FormInput from './FormInput';
4 | import SheetButton from './SheetButton';
5 |
6 | // This is a wrapper for google.script.run that lets us use promises.
7 | import { serverFunctions } from '../../utils/serverFunctions';
8 |
9 | const SheetEditor = () => {
10 | const [names, setNames] = useState([]);
11 |
12 | useEffect(() => {
13 | // Call a server global function here and handle the response with .then() and .catch()
14 | serverFunctions.getSheetsData().then(setNames).catch(alert);
15 | }, []);
16 |
17 | const deleteSheet = (sheetIndex) => {
18 | serverFunctions.deleteSheet(sheetIndex).then(setNames).catch(alert);
19 | };
20 |
21 | const setActiveSheet = (sheetName) => {
22 | serverFunctions.setActiveSheet(sheetName).then(setNames).catch(alert);
23 | };
24 |
25 | // You can also use async/await notation for server calls with our server wrapper.
26 | // (This does the same thing as .then().catch() in the above handlers.)
27 | const submitNewSheet = async (newSheetName) => {
28 | try {
29 | const response = await serverFunctions.addSheet(newSheetName);
30 | setNames(response);
31 | } catch (error) {
32 | // eslint-disable-next-line no-alert
33 | alert(error);
34 | }
35 | };
36 |
37 | return (
38 |
39 |
40 | ☀️ React demo! ☀️
41 |
42 |
43 | This is a sample page that demonstrates a simple React app. Enter a name
44 | for a new sheet, hit enter and the new sheet will be created. Click the
45 | red × next to the sheet name to delete it.
46 |
17 | );
18 | };
19 |
20 | export default SheetButton;
21 |
22 | SheetButton.propTypes = {
23 | sheetDetails: PropTypes.shape({
24 | index: PropTypes.number,
25 | name: PropTypes.string,
26 | isActive: PropTypes.bool,
27 | }),
28 | deleteSheet: PropTypes.func,
29 | setActiveSheet: PropTypes.func,
30 | };
31 |
--------------------------------------------------------------------------------
/src/client/dialog-demo/components/SheetEditor.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { TransitionGroup, CSSTransition } from 'react-transition-group';
3 | import FormInput from './FormInput';
4 | import SheetButton from './SheetButton';
5 |
6 | // This is a wrapper for google.script.run that lets us use promises.
7 | import { serverFunctions } from '../../utils/serverFunctions';
8 |
9 | const SheetEditor = () => {
10 | const [names, setNames] = useState([]);
11 |
12 | useEffect(() => {
13 | // Call a server global function here and handle the response with .then() and .catch()
14 | serverFunctions.getSheetsData().then(setNames).catch(alert);
15 | }, []);
16 |
17 | const deleteSheet = (sheetIndex) => {
18 | serverFunctions.deleteSheet(sheetIndex).then(setNames).catch(alert);
19 | };
20 |
21 | const setActiveSheet = (sheetName) => {
22 | serverFunctions.setActiveSheet(sheetName).then(setNames).catch(alert);
23 | };
24 |
25 | // You can also use async/await notation for server calls with our server wrapper.
26 | // (This does the same thing as .then().catch() in the above handlers.)
27 | const submitNewSheet = async (newSheetName) => {
28 | try {
29 | const response = await serverFunctions.addSheet(newSheetName);
30 | setNames(response);
31 | } catch (error) {
32 | // eslint-disable-next-line no-alert
33 | alert(error);
34 | }
35 | };
36 |
37 | return (
38 |
39 |
40 | ☀️ React demo! ☀️
41 |
42 |
43 | This is a sample page that demonstrates a simple React app. Enter a name
44 | for a new sheet, hit enter and the new sheet will be created. Click the
45 | red × next to the sheet name to delete it.
46 |