├── .babelrc ├── .gitignore ├── README.md ├── google-sheet-connector ├── gapi.js └── index.js ├── index.js ├── package.json ├── reactGoogleSheets.js └── src ├── ReactGoogleSheets.js ├── google-sheet-connector ├── gapi.js └── index.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react google sheets 2 | 3 | Simplely Add Google Sheets API to your react app 4 | 5 | # Quickstart 6 | 7 | ## First, install the library: 8 | ```shell 9 | npm install --save react-google-sheets 10 | ``` 11 | 12 | or 13 | 14 | ```shell 15 | yarn add react-google-sheets 16 | ``` 17 | 18 | ## Second, Turn on the Google Sheets API: 19 | 20 | - Use this [wizard](https://console.developers.google.com/flows/enableapi?apiid=sheets.googleapis.com) to create or select a project in the Google Developers Console and automatically turn on the API. Click **Continue**, then **Go to credentials**. 21 | 22 | - On the **Add credentials to your project** page, click the **Cancel** button. 23 | 24 | - At the top of the page, select the **OAuth consent screen** tab. Select an **Email address**, enter a **Product name** if not already set, and click the Save button. 25 | - Select the **Credentials** tab, click the **Create credentials** button and select **OAuth client ID**. 26 | - Select the application type **Other**, enter the name "React Google Sheets Quickstart", and click the **Create** button. 27 | 28 | ## Finally, Use the library: 29 | 30 | ```javascript 31 | import ReactGoogleSheets from 'react-google-sheets'; 32 | 33 | 34 | class DataComponent extends Component { 35 | constructor(props) { 36 | super(props) 37 | this.state = { 38 | sheetLoaded: false, 39 | } 40 | } 41 | render() { 42 | return ( 43 | this.setState({sheetLoaded: true})} 48 | > 49 | {this.state.sheetLoaded ? 50 |
51 | {/* Access Data */} 52 | {console.log('Your sheet data : ', this.props.getSheetsData({YOUR_SPREADSHEET_NAME}))} 53 | {/* Update Data */} 54 | 66 |
67 | : 68 | 'loading...' 69 | } 70 |
71 | ) 72 | } 73 | } 74 | 75 | export default ReactGoogleSheets.connect(DataComponent); 76 | 77 | 78 | ``` 79 | 80 | 81 | That's It! 82 | -------------------------------------------------------------------------------- /google-sheet-connector/gapi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | var gapi = window.gapi = window.gapi || {};gapi._bs = new Date().getTime();(function () { 6 | /* 7 | gapi.loader.OBJECT_CREATE_TEST_OVERRIDE &&*/ 8 | var g = window, 9 | h = document, 10 | m = g.location, 11 | n = function n() {}, 12 | q = /\[native code\]/, 13 | u = function u(a, b, c) { 14 | return a[b] = a[b] || c; 15 | }, 16 | aa = function aa(a) { 17 | a = a.sort();for (var b = [], c = void 0, d = 0; d < a.length; d++) { 18 | var e = a[d];e != c && b.push(e);c = e; 19 | }return b; 20 | }, 21 | w = function w() { 22 | var a;if ((a = Object.create) && q.test(a)) a = a(null);else { 23 | a = {};for (var b in a) { 24 | a[b] = void 0; 25 | } 26 | }return a; 27 | }, 28 | z = u(g, "gapi", {});var A;A = u(g, "___jsl", w());u(A, "I", 0);u(A, "hel", 10);var B = function B() { 29 | var a = m.href;if (A.dpo) var b = A.h;else { 30 | b = A.h;var c = /([#].*&|[#])jsh=([^&#]*)/g, 31 | d = /([?#].*&|[?#])jsh=([^&#]*)/g;if (a = a && (c.exec(a) || d.exec(a))) try { 32 | b = decodeURIComponent(a[2]); 33 | } catch (e) {} 34 | }return b; 35 | }, 36 | ba = function ba(a) { 37 | var b = u(A, "PQ", []);A.PQ = [];var c = b.length;if (0 === c) a();else for (var d = 0, e = function e() { 38 | ++d === c && a(); 39 | }, f = 0; f < c; f++) { 40 | b[f](e); 41 | } 42 | }, 43 | C = function C(a) { 44 | return u(u(A, "H", w()), a, w()); 45 | };var D = u(A, "perf", w()), 46 | F = u(D, "g", w()), 47 | ca = u(D, "i", w());u(D, "r", []);w();w();var G = function G(a, b, c) { 48 | var d = D.r;"function" === typeof d ? d(a, b, c) : d.push([a, b, c]); 49 | }, 50 | K = function K(a, b, c) { 51 | b && 0 < b.length && (b = J(b), c && 0 < c.length && (b += "___" + J(c)), 28 < b.length && (b = b.substr(0, 28) + (b.length - 28)), c = b, b = u(ca, "_p", w()), u(b, c, w())[a] = new Date().getTime(), G(a, "_p", c)); 52 | }, 53 | J = function J(a) { 54 | return a.join("__").replace(/\./g, "_").replace(/\-/g, "_").replace(/,/g, "_"); 55 | };var L = w(), 56 | M = [], 57 | N = function N(a) { 58 | throw Error("Bad hint" + (a ? ": " + a : "")); 59 | };M.push(["jsl", function (a) { 60 | for (var b in a) { 61 | if (Object.prototype.hasOwnProperty.call(a, b)) { 62 | var c = a[b];"object" == (typeof c === "undefined" ? "undefined" : _typeof(c)) ? A[b] = u(A, b, []).concat(c) : u(A, b, c); 63 | } 64 | }if (b = a.u) a = u(A, "us", []), a.push(b), (b = /^https:(.*)$/.exec(b)) && a.push("http:" + b[1]); 65 | }]);var da = /^(\/[a-zA-Z0-9_\-]+)+$/, 66 | O = [/\/amp\//, /\/amp$/, /^\/amp$/], 67 | ea = /^[a-zA-Z0-9\-_\.,!]+$/, 68 | fa = /^gapi\.loaded_[0-9]+$/, 69 | ha = /^[a-zA-Z0-9,._-]+$/, 70 | la = function la(a, b, c, d) { 71 | var e = a.split(";"), 72 | f = e.shift(), 73 | l = L[f], 74 | k = null;l ? k = l(e, b, c, d) : N("no hint processor for: " + f);k || N("failed to generate load url");b = k;c = b.match(ia);(d = b.match(ja)) && 1 === d.length && ka.test(b) && c && 1 === c.length || N("failed sanity: " + a);return k; 75 | }, 76 | na = function na(a, b, c, d) { 77 | a = ma(a);fa.test(c) || N("invalid_callback");b = P(b);d = d && d.length ? P(d) : null;var e = function e(a) { 78 | return encodeURIComponent(a).replace(/%2C/g, ","); 79 | };return [encodeURIComponent(a.pathPrefix).replace(/%2C/g, ",").replace(/%2F/g, "/"), "/k=", e(a.version), "/m=", e(b), d ? "/exm=" + e(d) : "", "/rt=j/sv=1/d=1/ed=1", a.a ? "/am=" + e(a.a) : "", a.c ? "/rs=" + e(a.c) : "", a.f ? "/t=" + e(a.f) : "", "/cb=", e(c)].join(""); 80 | }, 81 | ma = function ma(a) { 82 | "/" !== a.charAt(0) && N("relative path");for (var b = a.substring(1).split("/"), c = []; b.length;) { 83 | a = b.shift();if (!a.length || 0 == a.indexOf(".")) N("empty/relative directory");else if (0 < a.indexOf("=")) { 84 | b.unshift(a); 85 | break; 86 | }c.push(a); 87 | }a = {};for (var d = 0, e = b.length; d < e; ++d) { 88 | var f = b[d].split("="), 89 | l = decodeURIComponent(f[0]), 90 | k = decodeURIComponent(f[1]);2 == f.length && l && k && (a[l] = a[l] || k); 91 | }b = "/" + c.join("/");da.test(b) || N("invalid_prefix");c = 0;for (d = O.length; c < d; ++c) { 92 | O[c].test(b) && N("invalid_prefix"); 93 | }c = Q(a, "k", !0);d = Q(a, "am");e = Q(a, "rs");a = Q(a, "t");return { pathPrefix: b, version: c, a: d, c: e, f: a }; 94 | }, 95 | P = function P(a) { 96 | for (var b = [], c = 0, d = a.length; c < d; ++c) { 97 | var e = a[c].replace(/\./g, "_").replace(/-/g, "_");ha.test(e) && b.push(e); 98 | }return b.join(","); 99 | }, 100 | Q = function Q(a, b, c) { 101 | a = a[b];!a && c && N("missing: " + b);if (a) { 102 | if (ea.test(a)) return a;N("invalid: " + b); 103 | }return null; 104 | }, 105 | ka = /^https?:\/\/[a-z0-9_.-]+\.google(rs)?\.com(:\d+)?\/[a-zA-Z0-9_.,!=\-\/]+$/, 106 | ja = /\/cb=/g, 107 | ia = /\/\//g, 108 | oa = function oa() { 109 | var a = B();if (!a) throw Error("Bad hint");return a; 110 | };L.m = function (a, b, c, d) { 111 | (a = a[0]) || N("missing_hint");return "https://apis.google.com" + na(a, b, c, d); 112 | };var R = decodeURI("%73cript"), 113 | S = /^[-+_0-9\/A-Za-z]+={0,2}$/, 114 | T = function T(a, b) { 115 | for (var c = [], d = 0; d < a.length; ++d) { 116 | var e = a[d], 117 | f;if (f = e) { 118 | a: { 119 | for (f = 0; f < b.length; f++) { 120 | if (b[f] === e) break a; 121 | }f = -1; 122 | }f = 0 > f; 123 | }f && c.push(e); 124 | }return c; 125 | }, 126 | U = function U() { 127 | var a = A.nonce;return void 0 !== a ? a && a === String(a) && a.match(S) ? a : A.nonce = null : h.querySelector ? (a = h.querySelector("script[nonce]")) ? (a = a.nonce || a.getAttribute("nonce") || "", a && a === String(a) && a.match(S) ? A.nonce = a : A.nonce = null) : null : null; 128 | }, 129 | pa = function pa(a) { 130 | if ("loading" != h.readyState) V(a);else { 131 | var b = U(), 132 | c = "";null !== b && (c = ' nonce="' + b + '"');h.write("<" + R + ' src="' + encodeURI(a) + '"' + c + ">"); 133 | } 134 | }, 135 | V = function V(a) { 136 | var b = h.createElement(R);b.setAttribute("src", a);a = U();null !== a && b.setAttribute("nonce", a);b.async = "true";(a = h.getElementsByTagName(R)[0]) ? a.parentNode.insertBefore(b, a) : (h.head || h.body || h.documentElement).appendChild(b); 137 | }, 138 | qa = function qa(a, b) { 139 | var c = b && b._c;if (c) for (var d = 0; d < M.length; d++) { 140 | var e = M[d][0], 141 | f = M[d][1];f && Object.prototype.hasOwnProperty.call(c, e) && f(c[e], a, b); 142 | } 143 | }, 144 | ra = function ra(a, b, c) { 145 | X(function () { 146 | var c = b === B() ? u(z, "_", w()) : w();c = u(C(b), "_", c);a(c); 147 | }, c); 148 | }, 149 | Z = function Z(a, b) { 150 | var c = b || {};"function" == typeof b && (c = {}, c.callback = b);qa(a, c);b = a ? a.split(":") : [];var d = c.h || oa(), 151 | e = u(A, "ah", w());if (e["::"] && b.length) { 152 | a = [];for (var f = null; f = b.shift();) { 153 | var l = f.split(".");l = e[f] || e[l[1] && "ns:" + l[0] || ""] || d;var k = a.length && a[a.length - 1] || null, 154 | v = k;k && k.hint == l || (v = { hint: l, b: [] }, a.push(v));v.b.push(f); 155 | }var x = a.length;if (1 < x) { 156 | var y = c.callback;y && (c.callback = function () { 157 | 0 == --x && y(); 158 | }); 159 | }for (; b = a.shift();) { 160 | Y(b.b, c, b.hint); 161 | } 162 | } else Y(b || [], c, d); 163 | }, 164 | Y = function Y(a, b, c) { 165 | a = aa(a) || [];var d = b.callback, 166 | e = b.config, 167 | f = b.timeout, 168 | l = b.ontimeout, 169 | k = b.onerror, 170 | v = void 0;"function" == typeof k && (v = k);var x = null, 171 | y = !1;if (f && !l || !f && l) throw "Timeout requires both the timeout parameter and ontimeout parameter to be set";k = u(C(c), "r", []).sort();var H = u(C(c), "L", []).sort(), 172 | E = [].concat(k), 173 | W = function W(a, b) { 174 | if (y) return 0;g.clearTimeout(x);H.push.apply(H, p);var d = ((z || {}).config || {}).update;d ? d(e) : e && u(A, "cu", []).push(e);if (b) { 175 | K("me0", a, E);try { 176 | ra(b, c, v); 177 | } finally { 178 | K("me1", a, E); 179 | } 180 | }return 1; 181 | };0 < f && (x = g.setTimeout(function () { 182 | y = !0;l(); 183 | }, f));var p = T(a, H);if (p.length) { 184 | p = T(a, k);var r = u(A, "CP", []), 185 | t = r.length;r[t] = function (a) { 186 | if (!a) return 0;K("ml1", p, E);var b = function b(_b) { 187 | r[t] = null;W(p, a) && ba(function () { 188 | d && d();_b(); 189 | }); 190 | }, 191 | c = function c() { 192 | var a = r[t + 1];a && a(); 193 | };0 < t && r[t - 1] ? r[t] = function () { 194 | b(c); 195 | } : b(c); 196 | };if (p.length) { 197 | var I = "loaded_" + A.I++;z[I] = function (a) { 198 | r[t](a);z[I] = null; 199 | };a = la(c, p, "gapi." + I, k);k.push.apply(k, p);K("ml0", p, E);b.sync || g.___gapisync ? pa(a) : V(a); 200 | } else r[t](n); 201 | } else W(p) && d && d(); 202 | };var X = function X(a, b) { 203 | if (A.hee && 0 < A.hel) try { 204 | return a(); 205 | } catch (c) { 206 | b && b(c), A.hel--, Z("debug_error", function () { 207 | try { 208 | window.___jsl.hefn(c); 209 | } catch (d) { 210 | throw c; 211 | } 212 | }); 213 | } else try { 214 | return a(); 215 | } catch (c) { 216 | throw b && b(c), c; 217 | } 218 | };z.load = function (a, b) { 219 | return X(function () { 220 | return Z(a, b); 221 | }); 222 | };F.bs0 = window.gapi._bs || new Date().getTime();G("bs0");F.bs1 = new Date().getTime();G("bs1");delete window.gapi._bs; 223 | }).call(undefined); 224 | gapi.load("", { callback: window["gapi_onload"], _c: { "jsl": { "ci": { "deviceType": "desktop", "oauth-flow": { "authUrl": "https://accounts.google.com/o/oauth2/auth", "proxyUrl": "https://accounts.google.com/o/oauth2/postmessageRelay", "disableOpt": true, "idpIframeUrl": "https://accounts.google.com/o/oauth2/iframe", "usegapi": false }, "debug": { "reportExceptionRate": 0.05, "forceIm": false, "rethrowException": false, "host": "https://apis.google.com" }, "enableMultilogin": true, "googleapis.config": { "auth": { "useFirstPartyAuthV2": true } }, "isPlusUser": true, "inline": { "css": 1 }, "disableRealtimeCallback": false, "drive_share": { "skipInitCommand": true }, "csi": { "rate": 0.01 }, "client": { "cors": false }, "isLoggedIn": true, "signInDeprecation": { "rate": 0.0 }, "include_granted_scopes": true, "llang": "ko", "iframes": { "ytsubscribe": { "url": "https://www.youtube.com/subscribe_embed?usegapi=1" }, "plus_share": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix::se:_/+1/sharebutton?plusShare=true&usegapi=1" }, ":source:": "3p", "playemm": { "url": "https://play.google.com/work/embedded/search?usegapi=1&usegapi=1" }, "partnersbadge": { "url": "https://www.gstatic.com/partners/badge/templates/badge.html?usegapi=1" }, "dataconnector": { "url": "https://dataconnector.corp.google.com/:session_prefix:ui/widgetview?usegapi=1" }, "shortlists": { "url": "" }, "plus_followers": { "params": { "url": "" }, "url": ":socialhost:/_/im/_/widget/render/plus/followers?usegapi=1" }, "post": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix::im_prefix:_/widget/render/post?usegapi=1" }, "signin": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix:_/widget/render/signin?usegapi=1", "methods": ["onauth"] }, "donation": { "url": "https://onetoday.google.com/home/donationWidget?usegapi=1" }, "plusone": { "params": { "count": "", "size": "", "url": "" }, "url": ":socialhost:/:session_prefix::se:_/+1/fastbutton?usegapi=1" }, ":im_socialhost:": "https://plus.googleapis.com", "backdrop": { "url": "https://clients3.google.com/cast/chromecast/home/widget/backdrop?usegapi=1" }, "visibility": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix:_/widget/render/visibility?usegapi=1" }, "additnow": { "url": "https://apis.google.com/additnow/additnow.html?usegapi=1", "methods": ["launchurl"] }, ":signuphost:": "https://plus.google.com", "community": { "url": ":ctx_socialhost:/:session_prefix::im_prefix:_/widget/render/community?usegapi=1" }, "plus": { "url": ":socialhost:/:session_prefix:_/widget/render/badge?usegapi=1" }, "commentcount": { "url": ":socialhost:/:session_prefix:_/widget/render/commentcount?usegapi=1" }, "zoomableimage": { "url": "https://ssl.gstatic.com/microscope/embed/" }, "appfinder": { "url": "https://gsuite.google.com/:session_prefix:marketplace/appfinder?usegapi=1" }, "person": { "url": ":socialhost:/:session_prefix:_/widget/render/person?usegapi=1" }, "savetodrive": { "url": "https://drive.google.com/savetodrivebutton?usegapi=1", "methods": ["save"] }, "page": { "url": ":socialhost:/:session_prefix:_/widget/render/page?usegapi=1" }, "card": { "url": ":socialhost:/:session_prefix:_/hovercard/card" }, "youtube": { "params": { "location": ["search", "hash"] }, "url": ":socialhost:/:session_prefix:_/widget/render/youtube?usegapi=1", "methods": ["scroll", "openwindow"] }, "plus_circle": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix::se:_/widget/plus/circle?usegapi=1" }, "rbr_s": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix::se:_/widget/render/recobarsimplescroller" }, "udc_webconsentflow": { "params": { "url": "" }, "url": "https://myaccount.google.com/webconsent?usegapi=1" }, "savetoandroidpay": { "url": "https://androidpay.google.com/a/widget/save" }, "blogger": { "params": { "location": ["search", "hash"] }, "url": ":socialhost:/:session_prefix:_/widget/render/blogger?usegapi=1", "methods": ["scroll", "openwindow"] }, "evwidget": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix:_/events/widget?usegapi=1" }, "surveyoptin": { "url": "https://www.google.com/shopping/customerreviews/optin?usegapi=1" }, ":socialhost:": "https://apis.google.com", "hangout": { "url": "https://talkgadget.google.com/:session_prefix:talkgadget/_/widget" }, ":gplus_url:": "https://plus.google.com", "rbr_i": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix::se:_/widget/render/recobarinvitation" }, "share": { "url": ":socialhost:/:session_prefix::im_prefix:_/widget/render/share?usegapi=1" }, "comments": { "params": { "location": ["search", "hash"] }, "url": ":socialhost:/:session_prefix:_/widget/render/comments?usegapi=1", "methods": ["scroll", "openwindow"] }, "autocomplete": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix:_/widget/render/autocomplete" }, "ratingbadge": { "url": "https://www.google.com/shopping/customerreviews/badge?usegapi=1" }, "appcirclepicker": { "url": ":socialhost:/:session_prefix:_/widget/render/appcirclepicker" }, "follow": { "url": ":socialhost:/:session_prefix:_/widget/render/follow?usegapi=1" }, "sharetoclassroom": { "url": "https://www.gstatic.com/classroom/sharewidget/widget_stable.html?usegapi=1" }, "ytshare": { "params": { "url": "" }, "url": ":socialhost:/:session_prefix:_/widget/render/ytshare?usegapi=1" }, "family_creation": { "params": { "url": "" }, "url": "https://families.google.com/webcreation?usegapi=1&usegapi=1" }, "configurator": { "url": ":socialhost:/:session_prefix:_/plusbuttonconfigurator?usegapi=1" }, "savetowallet": { "url": "https://androidpay.google.com/a/widget/save" } } }, "h": "m;/_/scs/apps-static/_/js/k=oz.gapi.ko.u0WhFdqJrXQ.O/m=__features__/am=QQE/rt=j/d=1/rs=AGLTcCO-v0Extr2gWwJAKxa0xtQS573uyA", "u": "https://apis.google.com/js/api.js", "hee": true, "fp": "cf9b1a3b781a6a354bd124f14e52b66f3c506842", "dpo": false }, "fp": "cf9b1a3b781a6a354bd124f14e52b66f3c506842", "annotation": ["interactivepost", "recobar", "signin2", "autocomplete", "profile"], "bimodal": ["signin", "share"] } }); 225 | 226 | module.exports = gapi; -------------------------------------------------------------------------------- /google-sheet-connector/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | var fetch = require("isomorphic-fetch"); 6 | var camelCase = require("camelcase"); 7 | // subtituted code of at index.html 8 | var gapi = require("./gapi"); 9 | 10 | function GoogleSheetConnector(options, onLoad) { 11 | var _this = this; 12 | 13 | var sheetsData = []; 14 | this.sheetsLoaded = 0; 15 | this.apiKey = options.apiKey; 16 | this.clientId = options.clientId; 17 | this.spreadsheetId = options.spreadsheetId; 18 | 19 | this.initialise(); 20 | 21 | this.getSheetsData = function () { 22 | return sheetsData.slice(); 23 | }; 24 | 25 | this.setSheetsData = function (data) { 26 | sheetsData = data; 27 | 28 | this.sheetsLoaded++; 29 | 30 | if (this.sheetsLoaded === this.numSheets) { 31 | onDataLoaded.call(this); 32 | } 33 | }; 34 | 35 | function onDataLoaded() { 36 | this.setSheetsData = null; 37 | console.info("Data successfuly loaded from Spreadsheet"); 38 | if (onLoad) { 39 | onLoad.call(this); 40 | } 41 | } 42 | this.updateCell = function (sheetName, column, row, value, successCallback, errorCallback) { 43 | var data = { 44 | spreadsheetId: _this.spreadsheetId, 45 | range: sheetName + '!' + column + row, 46 | valueInputOption: 'USER_ENTERED', 47 | values: [[value]] 48 | }; 49 | gapi.client.sheets.spreadsheets.values.update(data).then(successCallback, errorCallback); 50 | }; 51 | } 52 | 53 | GoogleSheetConnector.prototype = { 54 | 55 | initialise: function initialise() { 56 | console.info("Loading data from Spreadsheet"); 57 | if (this.clientId) { 58 | return gapi.load("client:auth2", this.initClient.bind(this)); 59 | } else if (this.apiKey) { 60 | var url = ["https://sheets.googleapis.com/v4/spreadsheets/", this.spreadsheetId, "?key=", this.apiKey].join(""); 61 | 62 | fetch(url).then(function (response) { 63 | return response.json(); 64 | }).then(function (data) { 65 | return this.loadSheetsData(data); 66 | }.bind(this)); 67 | } else { 68 | console.info("You must specify a valid Client ID or API Key"); 69 | } 70 | }, 71 | 72 | loadSheetsData: function loadSheetsData(data) { 73 | this.numSheets = data.sheets.length; 74 | data.sheets.forEach(function (sheet) { 75 | return this.loadSheetViaKey(sheet.properties.title); 76 | }, this); 77 | }, 78 | 79 | loadSpreadsheet: function loadSpreadsheet() { 80 | gapi.client.sheets.spreadsheets.get({ 81 | spreadsheetId: this.spreadsheetId 82 | }).then(function (response) { 83 | var sheets = JSON.parse(response.body).sheets; 84 | this.numSheets = sheets.length; 85 | sheets.forEach(this.loadSheetViaAuth, this); 86 | }.bind(this)); 87 | }, 88 | 89 | loadSheetViaAuth: function loadSheetViaAuth(sheet) { 90 | gapi.client.sheets.spreadsheets.values.get({ 91 | spreadsheetId: this.spreadsheetId, 92 | range: sheet.properties.title 93 | }).then(function (response) { 94 | var values = JSON.parse(response.body).values; 95 | this.loadSheet(sheet.properties.title, values); 96 | }.bind(this)); 97 | }, 98 | 99 | loadSheetViaKey: function loadSheetViaKey(sheetName) { 100 | var url = ["https://sheets.googleapis.com/v4/spreadsheets/", this.spreadsheetId, "/values/", sheetName, "?key=", this.apiKey].join(""); 101 | 102 | fetch(url).then(function (response) { 103 | return response.json(); 104 | }).then(function (json) { 105 | var values = json.values; 106 | this.loadSheet(sheetName, values); 107 | }.bind(this)); 108 | }, 109 | 110 | loadSheet: function loadSheet(sheetName, values) { 111 | var headerRow = values[0]; 112 | var dataRows = values.slice(1); 113 | var keys = headerRow.map(function (value) { 114 | return camelCase(value); 115 | }, this); 116 | 117 | var sheetsData = this.getSheetsData(); 118 | 119 | sheetsData = sheetsData.concat({ 120 | name: sheetName, 121 | header: headerRow, 122 | keys: keys, 123 | data: this.loadRowsData(keys, dataRows) 124 | }); 125 | 126 | this.setSheetsData(sheetsData); 127 | }, 128 | 129 | loadRowsData: function loadRowsData(keys, values) { 130 | return values.map(function (row) { 131 | 132 | keys.forEach(function (key, i) { 133 | row[key] = row[i]; 134 | }); 135 | 136 | return row; 137 | }); 138 | }, 139 | 140 | initClient: function initClient() { 141 | gapi.client.init({ 142 | discoveryDocs: ["https://sheets.googleapis.com/$discovery/rest?version=v4"], 143 | clientId: this.clientId, 144 | scope: "https://www.googleapis.com/auth/spreadsheets" 145 | }).then(function () { 146 | var authInstance = gapi.auth2.getAuthInstance(); 147 | if (authInstance.isSignedIn.get()) { 148 | this.loadSpreadsheet(); 149 | } else { 150 | authInstance.isSignedIn.listen(this.updateSigninStatus.bind(this)); 151 | authInstance.signIn(); 152 | } 153 | }.bind(this)); 154 | }, 155 | 156 | updateSigninStatus: function updateSigninStatus(isSignedIn) { 157 | if (isSignedIn) { 158 | this.loadSpreadsheet(); 159 | } 160 | }, 161 | 162 | getSheet: function getSheet(sheetName) { 163 | return new SheetData(this.getSheetsData(), sheetName); 164 | } 165 | }; 166 | 167 | function SheetData(sheetsData, sheetName) { 168 | var sheet = sheetsData.find(function (sheet) { 169 | return sheet.name === sheetName; 170 | }) || { data: [], values: [] }; 171 | 172 | this.header = sheet.header; 173 | this.keys = sheet.keys; 174 | var data = sheet.data; 175 | var currentData = data.slice(); 176 | 177 | this.getData = function () { 178 | return data.slice(); 179 | }; 180 | 181 | this.getCurrentData = function () { 182 | return currentData.slice(); 183 | }; 184 | 185 | this.setCurrentData = function (newData) { 186 | currentData = newData; 187 | }; 188 | } 189 | 190 | SheetData.prototype = { 191 | map: function map(callback) { 192 | return this.getCurrentData().map(callback); 193 | }, 194 | filter: function filter(filterObj, strValue) { 195 | var newData = this.getData().filter(function (row) { 196 | if ((typeof filterObj === "undefined" ? "undefined" : _typeof(filterObj)) === "object") { 197 | for (var i in filterObj) { 198 | if (!row.hasOwnProperty(i) || row[i] !== filterObj[i]) { 199 | return false; 200 | } 201 | } 202 | } else { 203 | var colIndex = this.header.indexOf(filterObj); 204 | if (row[colIndex] !== strValue) return false; 205 | } 206 | 207 | return true; 208 | }, this); 209 | 210 | this.setCurrentData(newData); 211 | 212 | return this; 213 | }, 214 | group: function group(colName, sort) { 215 | var groups = []; 216 | var colIndex = this.header.indexOf(colName); 217 | 218 | if (colIndex === -1) return this; 219 | 220 | this.getCurrentData().forEach(function (row) { 221 | var groupName = row[colIndex]; 222 | var groupIndex = -1; 223 | 224 | groups.forEach(function (group, i) { 225 | if (group.name === groupName) groupIndex = i; 226 | }); 227 | 228 | if (groupIndex > -1) { 229 | groups[groupIndex].data.push(row); 230 | } else { 231 | groups.push({ 232 | name: groupName, 233 | data: [row] 234 | }); 235 | } 236 | }); 237 | 238 | if (sort) sortArray(groups, "name"); 239 | 240 | this.setCurrentData(groups); 241 | this.dataIsGrouped = true; 242 | 243 | return this; 244 | }, 245 | sort: function sort(colName) { 246 | var newData = this.getCurrentData(); 247 | if (this.dataIsGrouped) { 248 | newData.forEach(function (group) { 249 | sortArray(group.data, camelCase(colName)); 250 | }); 251 | } else { 252 | sortArray(newData, camelCase(colName)); 253 | } 254 | this.setCurrentData(newData); 255 | 256 | return this; 257 | }, 258 | reverse: function reverse() { 259 | var newData = this.getCurrentData(); 260 | newData.reverse(); 261 | this.setCurrentData(newData); 262 | return this; 263 | } 264 | }; 265 | 266 | function sortArray(array, orderBy) { 267 | 268 | array.sort(function (a, b) { 269 | var textA = a[orderBy] ? a[orderBy].toUpperCase() : ""; 270 | var textB = b[orderBy] ? b[orderBy].toUpperCase() : ""; 271 | if (textA < textB) return -1; 272 | return textA > textB ? 1 : 0; 273 | }); 274 | } 275 | 276 | module.exports = GoogleSheetConnector; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _ReactGoogleSheets = require('./ReactGoogleSheets'); 8 | 9 | var _ReactGoogleSheets2 = _interopRequireDefault(_ReactGoogleSheets); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | _ReactGoogleSheets2.default.connect = _ReactGoogleSheets.connectToSpreadsheet; 14 | exports.default = _ReactGoogleSheets2.default; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-google-sheets", 3 | "version": "0.3.0", 4 | "description": "Read and Write data using Google Sheets API in react", 5 | "scripts": { 6 | "dev": "./node_modules/.bin/babel --watch src -d ./" 7 | }, 8 | "author": "ruucm ", 9 | "dependencies": { 10 | "camelcase": "5.0.0", 11 | "isomorphic-fetch": "^2.2.1" 12 | }, 13 | "license": "GPLv3", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ruucm/react-google-sheets" 17 | }, 18 | "homepage": "https://github.com/ruucm/react-google-sheets", 19 | "devDependencies": { 20 | "babel-cli": "^6.26.0", 21 | "babel-preset-env": "^1.7.0", 22 | "babel-preset-es2015": "^6.24.1", 23 | "babel-preset-react": "^6.24.1", 24 | "babel-preset-stage-0": "^6.24.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reactGoogleSheets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.connectToSpreadsheet = exports.ReactGoogleSheets = undefined; 7 | 8 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 9 | 10 | var _react = require('react'); 11 | 12 | var _react2 = _interopRequireDefault(_react); 13 | 14 | var _googleSheetConnector = require('./google-sheet-connector'); 15 | 16 | var _googleSheetConnector2 = _interopRequireDefault(_googleSheetConnector); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 21 | 22 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 23 | 24 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 25 | 26 | var googleSheet; 27 | 28 | /* Components */ 29 | 30 | var ReactGoogleSheets = exports.ReactGoogleSheets = function (_Component) { 31 | _inherits(ReactGoogleSheets, _Component); 32 | 33 | function ReactGoogleSheets() { 34 | _classCallCheck(this, ReactGoogleSheets); 35 | 36 | return _possibleConstructorReturn(this, (ReactGoogleSheets.__proto__ || Object.getPrototypeOf(ReactGoogleSheets)).apply(this, arguments)); 37 | } 38 | 39 | _createClass(ReactGoogleSheets, [{ 40 | key: 'componentDidMount', 41 | value: function componentDidMount() { 42 | googleSheet = new _googleSheetConnector2.default({ 43 | apiKey: this.props.apiKey, 44 | clientId: this.props.clientId, 45 | spreadsheetId: this.props.spreadsheetId 46 | }, function () { 47 | this.props.afterLoading(); 48 | }.bind(this)); 49 | } 50 | }, { 51 | key: 'render', 52 | value: function render() { 53 | return this.props.children || null; 54 | } 55 | }]); 56 | 57 | return ReactGoogleSheets; 58 | }(_react.Component); 59 | 60 | var connectToSpreadsheet = exports.connectToSpreadsheet = function connectToSpreadsheet(component) { 61 | return function (props) { 62 | var newProps = { 63 | getSheetsData: function getSheetsData(sheetName) { 64 | return googleSheet.getSheetsData(sheetName); 65 | }, 66 | updateCell: function updateCell(sheetName, column, row, value, successCallback, errorCallback) { 67 | return googleSheet.updateCell(sheetName, column, row, value, successCallback, errorCallback); 68 | } 69 | }; 70 | 71 | for (var i in props) { 72 | newProps[i] = props[i]; 73 | } 74 | 75 | return _react2.default.createElement(component, newProps); 76 | }; 77 | }; 78 | 79 | exports.default = ReactGoogleSheets; -------------------------------------------------------------------------------- /src/ReactGoogleSheets.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import GoogleSheetConnector from './google-sheet-connector' 4 | 5 | 6 | var googleSheet; 7 | 8 | /* Components */ 9 | export class ReactGoogleSheets extends Component { 10 | componentDidMount() { 11 | googleSheet = new GoogleSheetConnector({ 12 | apiKey: this.props.apiKey, 13 | clientId: this.props.clientId, 14 | spreadsheetId: this.props.spreadsheetId 15 | }, function() { 16 | this.props.afterLoading(); 17 | }.bind(this)); 18 | } 19 | render() { 20 | return this.props.children || null 21 | } 22 | } 23 | 24 | export const connectToSpreadsheet = function(component) { 25 | return function(props) { 26 | var newProps = { 27 | getSheetsData: function(sheetName) { 28 | return googleSheet.getSheetsData(sheetName); 29 | }, 30 | updateCell: function(sheetName, column, row, value, successCallback, errorCallback) { 31 | return googleSheet.updateCell(sheetName, column, row, value, successCallback, errorCallback); 32 | }, 33 | }; 34 | 35 | for (var i in props) { 36 | newProps[i] = props[i]; 37 | } 38 | 39 | return React.createElement(component, newProps); 40 | }; 41 | }; 42 | 43 | export default ReactGoogleSheets; 44 | -------------------------------------------------------------------------------- /src/google-sheet-connector/gapi.js: -------------------------------------------------------------------------------- 1 | var gapi=window.gapi=window.gapi||{};gapi._bs=new Date().getTime();(function(){/* 2 | gapi.loader.OBJECT_CREATE_TEST_OVERRIDE &&*/ 3 | var g=window,h=document,m=g.location,n=function(){},q=/\[native code\]/,u=function(a,b,c){return a[b]=a[b]||c},aa=function(a){a=a.sort();for(var b=[],c=void 0,d=0;df}f&&c.push(e)}return c},U=function(){var a=A.nonce;return void 0!==a?a&&a===String(a)&&a.match(S)?a:A.nonce=null:h.querySelector?(a=h.querySelector("script[nonce]"))?(a=a.nonce||a.getAttribute("nonce")||"",a&&a===String(a)&&a.match(S)?A.nonce=a:A.nonce=null):null:null},pa=function(a){if("loading"!=h.readyState)V(a); 7 | else{var b=U(),c="";null!==b&&(c=' nonce="'+b+'"');h.write("<"+R+' src="'+encodeURI(a)+'"'+c+">")}},V=function(a){var b=h.createElement(R);b.setAttribute("src",a);a=U();null!==a&&b.setAttribute("nonce",a);b.async="true";(a=h.getElementsByTagName(R)[0])?a.parentNode.insertBefore(b,a):(h.head||h.body||h.documentElement).appendChild(b)},qa=function(a,b){var c=b&&b._c;if(c)for(var d=0;d at index.html 4 | var gapi = require("./gapi"); 5 | 6 | 7 | function GoogleSheetConnector(options, onLoad) { 8 | var sheetsData = []; 9 | this.sheetsLoaded = 0; 10 | this.apiKey = options.apiKey; 11 | this.clientId = options.clientId; 12 | this.spreadsheetId = options.spreadsheetId; 13 | 14 | this.initialise(); 15 | 16 | this.getSheetsData = function() { 17 | return sheetsData.slice(); 18 | }; 19 | 20 | this.setSheetsData = function (data) { 21 | sheetsData = data; 22 | 23 | this.sheetsLoaded ++; 24 | 25 | if (this.sheetsLoaded === this.numSheets) { 26 | onDataLoaded.call(this); 27 | } 28 | }; 29 | 30 | function onDataLoaded() { 31 | this.setSheetsData = null; 32 | console.info("Data successfuly loaded from Spreadsheet"); 33 | if (onLoad) { 34 | onLoad.call(this); 35 | } 36 | } 37 | this.updateCell = (sheetName, column, row, value, successCallback, errorCallback) => { 38 | var data = { 39 | spreadsheetId: this.spreadsheetId, 40 | range: sheetName + '!' + column + row, 41 | valueInputOption: 'USER_ENTERED', 42 | values: [ [value] ] 43 | } 44 | gapi.client.sheets.spreadsheets.values.update(data).then(successCallback, errorCallback); 45 | } 46 | } 47 | 48 | GoogleSheetConnector.prototype = { 49 | 50 | initialise: function() { 51 | console.info("Loading data from Spreadsheet"); 52 | if (this.clientId) { 53 | return gapi.load("client:auth2", this.initClient.bind(this)); 54 | } else if (this.apiKey) { 55 | var url = [ 56 | "https://sheets.googleapis.com/v4/spreadsheets/", 57 | this.spreadsheetId, 58 | "?key=", 59 | this.apiKey 60 | ].join(""); 61 | 62 | fetch(url) 63 | .then(function(response) { 64 | return response.json(); 65 | }) 66 | .then(function(data) { 67 | return this.loadSheetsData(data); 68 | }.bind(this)); 69 | } else { 70 | console.info("You must specify a valid Client ID or API Key"); 71 | } 72 | }, 73 | 74 | loadSheetsData: function(data) { 75 | this.numSheets = data.sheets.length; 76 | data.sheets.forEach(function(sheet) { 77 | return this.loadSheetViaKey(sheet.properties.title); 78 | }, this); 79 | }, 80 | 81 | loadSpreadsheet: function() { 82 | gapi.client.sheets.spreadsheets.get({ 83 | spreadsheetId: this.spreadsheetId 84 | }).then(function (response) { 85 | var sheets = JSON.parse(response.body).sheets; 86 | this.numSheets = sheets.length; 87 | sheets.forEach(this.loadSheetViaAuth, this); 88 | }.bind(this)); 89 | }, 90 | 91 | loadSheetViaAuth: function(sheet) { 92 | gapi.client.sheets.spreadsheets.values.get({ 93 | spreadsheetId: this.spreadsheetId, 94 | range: sheet.properties.title 95 | }).then(function(response) { 96 | var values = JSON.parse(response.body).values; 97 | this.loadSheet(sheet.properties.title, values); 98 | }.bind(this)); 99 | }, 100 | 101 | loadSheetViaKey: function(sheetName) { 102 | var url = [ 103 | "https://sheets.googleapis.com/v4/spreadsheets/", 104 | this.spreadsheetId, 105 | "/values/", 106 | sheetName, 107 | "?key=", 108 | this.apiKey 109 | ].join(""); 110 | 111 | fetch(url) 112 | .then(function(response) { return response.json(); }) 113 | .then(function(json) { 114 | var values = json.values; 115 | this.loadSheet(sheetName, values); 116 | }.bind(this)); 117 | }, 118 | 119 | loadSheet: function(sheetName, values) { 120 | var headerRow = values[0]; 121 | var dataRows = values.slice(1); 122 | var keys = headerRow.map(function(value) { 123 | return camelCase(value); 124 | }, this); 125 | 126 | var sheetsData = this.getSheetsData(); 127 | 128 | sheetsData = sheetsData.concat({ 129 | name: sheetName, 130 | header: headerRow, 131 | keys: keys, 132 | data: this.loadRowsData(keys, dataRows) 133 | }); 134 | 135 | this.setSheetsData(sheetsData); 136 | }, 137 | 138 | loadRowsData: function(keys, values) { 139 | return values.map(function(row) { 140 | 141 | keys.forEach(function(key, i) { 142 | row[key] = row[i]; 143 | }); 144 | 145 | return row; 146 | }); 147 | }, 148 | 149 | initClient: function() { 150 | gapi.client.init({ 151 | discoveryDocs: ["https://sheets.googleapis.com/$discovery/rest?version=v4"], 152 | clientId: this.clientId, 153 | scope: "https://www.googleapis.com/auth/spreadsheets" 154 | }).then(function () { 155 | var authInstance = gapi.auth2.getAuthInstance(); 156 | if (authInstance.isSignedIn.get()) { 157 | this.loadSpreadsheet() 158 | } else { 159 | authInstance.isSignedIn.listen(this.updateSigninStatus.bind(this)); 160 | authInstance.signIn(); 161 | } 162 | }.bind(this)); 163 | }, 164 | 165 | updateSigninStatus: function(isSignedIn) { 166 | if (isSignedIn) { 167 | this.loadSpreadsheet(); 168 | } 169 | }, 170 | 171 | getSheet: function(sheetName) { 172 | return new SheetData(this.getSheetsData(), sheetName); 173 | }, 174 | }; 175 | 176 | function SheetData(sheetsData, sheetName) { 177 | var sheet = sheetsData.find(function(sheet) { 178 | return sheet.name === sheetName; 179 | }) || {data: [], values: []}; 180 | 181 | this.header = sheet.header; 182 | this.keys = sheet.keys; 183 | var data = sheet.data; 184 | var currentData = data.slice(); 185 | 186 | this.getData = function() { 187 | return data.slice(); 188 | }; 189 | 190 | this.getCurrentData = function() { 191 | return currentData.slice(); 192 | }; 193 | 194 | this.setCurrentData = function(newData) { 195 | currentData = newData; 196 | }; 197 | } 198 | 199 | SheetData.prototype = { 200 | map: function(callback) { 201 | return this.getCurrentData().map(callback); 202 | }, 203 | filter: function(filterObj, strValue) { 204 | var newData = this.getData().filter(function(row) { 205 | if (typeof filterObj === "object") { 206 | for (var i in filterObj) { 207 | if (!row.hasOwnProperty(i) || row[i] !== filterObj[i]) { 208 | return false; 209 | } 210 | } 211 | } else { 212 | const colIndex = this.header.indexOf(filterObj); 213 | if (row[colIndex] !== strValue) return false; 214 | } 215 | 216 | return true; 217 | }, this); 218 | 219 | this.setCurrentData(newData); 220 | 221 | return this; 222 | }, 223 | group: function(colName, sort) { 224 | var groups = []; 225 | var colIndex = this.header.indexOf(colName); 226 | 227 | if (colIndex === -1) return this; 228 | 229 | this.getCurrentData().forEach(function(row) { 230 | var groupName = row[colIndex]; 231 | var groupIndex = -1; 232 | 233 | groups.forEach(function(group, i) { 234 | if (group.name === groupName) groupIndex = i; 235 | }); 236 | 237 | if (groupIndex > -1) { 238 | groups[groupIndex].data.push(row); 239 | } else { 240 | groups.push({ 241 | name: groupName, 242 | data: [row] 243 | }); 244 | } 245 | }); 246 | 247 | if (sort) sortArray(groups, "name"); 248 | 249 | this.setCurrentData(groups); 250 | this.dataIsGrouped = true; 251 | 252 | return this; 253 | }, 254 | sort: function(colName) { 255 | var newData = this.getCurrentData(); 256 | if (this.dataIsGrouped) { 257 | newData.forEach(function(group) { 258 | sortArray(group.data, camelCase(colName)); 259 | }); 260 | } else { 261 | sortArray(newData, camelCase(colName)); 262 | } 263 | this.setCurrentData(newData); 264 | 265 | return this; 266 | }, 267 | reverse: function() { 268 | var newData = this.getCurrentData(); 269 | newData.reverse(); 270 | this.setCurrentData(newData); 271 | return this; 272 | } 273 | }; 274 | 275 | function sortArray(array, orderBy) { 276 | 277 | array.sort(function(a, b) { 278 | var textA = a[orderBy] ? a[orderBy].toUpperCase() : ""; 279 | var textB = b[orderBy] ? b[orderBy].toUpperCase() : ""; 280 | if (textA < textB) return -1; 281 | return textA > textB ? 1 : 0; 282 | }); 283 | } 284 | 285 | module.exports = GoogleSheetConnector; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactGoogleSheets, { connectToSpreadsheet } from './ReactGoogleSheets' 2 | 3 | ReactGoogleSheets.connect = connectToSpreadsheet; 4 | export default ReactGoogleSheets; 5 | --------------------------------------------------------------------------------