Skip to content

Commit 6c68618

Browse files
authored
Merge pull request #17 from tryretool/bb/fix-demo-app
Bb/fix demo app
2 parents f8b0689 + 7b5f1a6 commit 6c68618

File tree

3 files changed

+128
-121
lines changed

3 files changed

+128
-121
lines changed

src/App.js

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,59 @@
1-
import Retool from "./components/Retool"
1+
import Retool from "./components/Retool";
2+
import { useState, useEffect } from "react";
23

3-
function App() {
4+
const App = () => {
5+
const sample = {
6+
example1: "",
7+
example2: false,
8+
input: "",
9+
};
10+
11+
useEffect(() => {
12+
const handler = (event) => {
13+
if (
14+
event.origin === "https://support.retool.com" &&
15+
event.data?.type !== "PARENT_WINDOW_QUERY"
16+
) {
17+
setRetoolData(event.data);
18+
}
19+
};
20+
21+
window.addEventListener("message", handler);
22+
23+
return () => window.removeEventListener("message", handler);
24+
}, []);
25+
26+
const [retoolData, setRetoolData] = useState("");
27+
const [data, setData] = useState(sample);
428
return (
5-
<Retool
6-
url="https://retoolin.tryretool.com/embedded/public/f7607e1f-670a-4ebf-9a09-be54cf17181e"
7-
data={{
8-
example: "value",
9-
}}
10-
></Retool>
11-
)
12-
}
13-
14-
export default App
29+
<div>
30+
<h1> React-Retool</h1>
31+
<button
32+
onClick={() => {
33+
setData({ ...data, example2: !data.example2 });
34+
}}
35+
>
36+
Click me!
37+
</button>
38+
<br />
39+
<br />
40+
<label> Share something: </label>
41+
<input
42+
type="text"
43+
value={data.input}
44+
onChange={(e) => setData({ ...data, input: e.target.value })}
45+
/>
46+
<br />
47+
<br />
48+
<Retool
49+
url="https://support.retool.com/embedded/public/cb9e15f0-5d7c-43a7-a746-cdec870dde9a"
50+
data={data}
51+
height="700px"
52+
width="1000px"
53+
></Retool>
54+
<h1> {retoolData} </h1>
55+
</div>
56+
);
57+
};
58+
59+
export default App;

src/components/Retool.js

Lines changed: 66 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,91 @@
1-
import React from "react"
1+
import { useEffect, useRef, useState } from "react";
22

3-
class Retool extends React.Component {
4-
constructor(props) {
5-
super(props)
3+
const Retool = ({ data, url, height, width }) => {
4+
const embeddedIframe = useRef(null);
5+
const [elementWatchers, setElementWatchers] = useState({});
66

7-
if (!this.props.url)
8-
throw new Error("Please pass a url into the Retool component.")
9-
10-
this.state = {
11-
url: props.url,
12-
elementWatchers: {},
13-
parentData: this.props.data || {},
14-
}
15-
}
16-
17-
componentDidMount() {
18-
this.startListening()
19-
this.startWatchers()
20-
}
21-
22-
startListening = () => {
23-
if (this.iframe) {
24-
window.addEventListener("message", (e) => this.handle(e))
7+
/* Retool passes up the list of elements to watch on page load */
8+
useEffect(() => {
9+
for (const key in elementWatchers) {
10+
const watcher = elementWatchers[key];
11+
watcher.iframe?.contentWindow.postMessage(
12+
{
13+
type: "PARENT_WINDOW_RESULT",
14+
result: data[watcher.selector],
15+
id: watcher.queryId,
16+
pageName: watcher.pageName,
17+
},
18+
"*"
19+
);
2520
}
26-
}
27-
28-
startWatchers = () => {
29-
var watcherKeys = Object.keys(this.state.elementWatchers)
30-
31-
for (var i = 0; i < watcherKeys.length; i++) {
32-
var key = watcherKeys[i]
33-
var watcher = this.state.elementWatchers[key]
34-
var selector = watcher.selector
35-
const value = this.dataFromSelector(selector)
36-
if (value !== watcher.prevValue) {
37-
watcher.prevValue = value
38-
watcher.iframe.contentWindow.postMessage(
39-
{
40-
type: "PARENT_WINDOW_RESULT",
41-
result: value,
42-
id: watcher.queryId,
43-
pageName: watcher.pageName,
44-
},
45-
"*"
46-
)
21+
}, [data, elementWatchers]);
22+
23+
/* On page load, add event listener to listen for events from Retool */
24+
useEffect(() => {
25+
/* Handle events - if PWQ then create/replace watchers -> return result */
26+
const handler = (event) => {
27+
if (!embeddedIframe?.current?.contentWindow) return;
28+
if (event.data.type === "PARENT_WINDOW_QUERY") {
29+
createOrReplaceWatcher(
30+
event.data.selector,
31+
event.data.pageName,
32+
event.data.id
33+
);
34+
postMessageForSelector("PARENT_WINDOW_RESULT", event.data);
4735
}
48-
}
36+
};
4937

50-
setTimeout(this.startWatchers, 100)
51-
}
38+
window.addEventListener("message", handler);
5239

53-
createOrReplaceWatcher = (selector, pageName, queryId) => {
54-
var watcherId = pageName + "-" + queryId
55-
var watchers = { ...this.state.elementWatchers }
40+
return () => window.removeEventListener("message", handler);
41+
}, []);
5642

57-
watchers[watcherId] = {
58-
iframe: this.iframe,
43+
/* Creates or updates the list of values for us to watch for changes */
44+
const createOrReplaceWatcher = (selector, pageName, queryId) => {
45+
const watcherId = pageName + "-" + queryId;
46+
const updatedState = elementWatchers;
47+
48+
updatedState[watcherId] = {
49+
iframe: embeddedIframe.current,
5950
selector: selector,
6051
pageName: pageName,
6152
queryId: queryId,
62-
prevValue: null,
63-
}
64-
65-
this.setState({ elementWatchers: watchers })
66-
}
53+
};
6754

68-
dataFromSelector = (selector) => {
69-
// Two places the app might be asking for data:
70-
// 1. The textContent of an HTML element.
71-
// 2. From data passed into this component
72-
const matchingInjectedData = this.state.parentData[selector]
73-
const nodeData = document.querySelector(selector)?.textContent
74-
return matchingInjectedData || nodeData || null
75-
}
55+
setElementWatchers(updatedState);
56+
};
7657

77-
postMessageForSelector = (messageType, eventData) => {
78-
const maybeData = this.dataFromSelector(eventData.selector)
58+
/* Checks for selectors for data and posts message for Retool to read */
59+
const postMessageForSelector = (messageType, eventData) => {
60+
const maybeData = data[eventData.selector];
7961

8062
if (maybeData) {
81-
this.iframe.contentWindow.postMessage(
63+
embeddedIframe.current.contentWindow.postMessage(
8264
{
8365
type: messageType,
8466
result: maybeData,
8567
id: eventData.id,
8668
pageName: eventData.pageName,
8769
},
8870
"*"
89-
)
71+
);
9072
} else {
9173
console.log(
9274
`Not sending data back to Retool, nothing found for selector: ${eventData.selector}`
93-
)
94-
}
95-
}
96-
97-
handle = (event) => {
98-
if (!this.iframe?.contentWindow) return
99-
if (event.data.type === "PARENT_WINDOW_QUERY") {
100-
this.createOrReplaceWatcher(
101-
event.data.selector,
102-
event.data.pageName,
103-
event.data.id
104-
)
105-
this.postMessageForSelector("PARENT_WINDOW_RESULT", event.data)
75+
);
10676
}
107-
108-
if (event.data.type === "PARENT_WINDOW_PREVIEW_QUERY") {
109-
this.postMessageForSelector("PARENT_WINDOW_PREVIEW_RESULT", event.data)
110-
}
111-
}
112-
113-
render() {
114-
return (
115-
<iframe
116-
height="100%"
117-
width="100%"
118-
frameBorder="none"
119-
src={this.state.url}
120-
ref={(e) => {
121-
this.iframe = e
122-
}}
123-
title="retool"
124-
></iframe>
125-
)
126-
}
127-
}
128-
129-
export default Retool
77+
};
78+
79+
return (
80+
<iframe
81+
height={height ?? "100%"}
82+
width={width ?? "100%"}
83+
frameBorder="none"
84+
src={url}
85+
ref={embeddedIframe}
86+
title="retool"
87+
></iframe>
88+
);
89+
};
90+
91+
export default Retool;

src/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React from "react"
2-
import ReactDOM from "react-dom"
3-
import "./index.css"
4-
import App from "./App"
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import "./index.css";
4+
import App from "./App";
55

66
ReactDOM.render(
77
<React.StrictMode>
88
<App />
99
</React.StrictMode>,
1010
document.getElementById("root")
11-
)
11+
);

0 commit comments

Comments
 (0)