Skip to content

Commit 3ddef11

Browse files
committed
Updated webapp-library example to only use function components
This transition replaced class component state and lifecycle methods with hooks and refactored some of the parent implementation to create and manage instances of libs in a more clean way.
1 parent b475782 commit 3ddef11

File tree

12 files changed

+313
-299
lines changed

12 files changed

+313
-299
lines changed

examples/webapp-library/parent/package.json

+12-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@
1616
"eject": "react-scripts eject"
1717
},
1818
"eslintConfig": {
19-
"extends": "react-app"
19+
"extends": "react-app",
20+
"plugins": [
21+
"react-hooks"
22+
],
23+
"rules": {
24+
"react-hooks/rules-of-hooks": "error",
25+
"react-hooks/exhaustive-deps": "warn"
26+
}
2027
},
2128
"browserslist": [
2229
">0.2%",
2330
"not dead",
2431
"not ie <= 11",
2532
"not op_mini all"
26-
]
33+
],
34+
"devDependencies": {
35+
"eslint-plugin-react-hooks": "^1.6.0"
36+
}
2737
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11

2-
import React, { Component } from 'react';
3-
import { string, arrayOf } from 'prop-types';
2+
import React from 'react';
43
import WebApiBridge from '@precor/web-api-bridge';
54

6-
// to keep track of created iframes that use webApiBridges
7-
const bridgedIframes = [];
5+
// createLibInstance factory, libs use apis to communicate through a bridge
6+
const createLibInstance = ({ webApiBridge, apis }) => ({
7+
webApiBridge,
8+
apis,
9+
findApiOfType(apiName) {
10+
return this.webApiBridge.apis.find(api => (api instanceof apiMap[apiName]));
11+
},
12+
});
13+
14+
// to keep track of created libs that use apis to communicate
15+
class libInstances {
16+
static instances = [];
17+
18+
static add = libInstance => libInstances.instances.push(libInstance);
19+
20+
static executeOnType = (apiName, fn) => {
21+
libInstances.instances.forEach((instance) => {
22+
const api = instance.findApiOfType(apiName);
23+
if (api) fn(api);
24+
});
25+
}
26+
}
827

928
class Common {
1029
setSend = (send) => {
@@ -19,6 +38,7 @@ class Common {
1938
this.send('displayGrayscale', [grayScale], false);
2039
};
2140
}
41+
2242
class Api1 {
2343
setSend = (send) => {
2444
this.send = send;
@@ -35,15 +55,8 @@ class Api2 {
3555
};
3656

3757
photoClicked = (id) => {
38-
const api1Iframe = bridgedIframes.find(bridgedIframe => (
39-
bridgedIframe.props.type === 'LibType1'
40-
));
41-
api1Iframe.getApiOfType('Api1').photoSelected(id);
42-
bridgedIframes.forEach((bridgedIframe) => {
43-
if (bridgedIframe.send !== this.send && bridgedIframe.props.type === 'LibType2') {
44-
bridgedIframe.getApiOfType('Api2').displayNewPhoto();
45-
}
46-
});
58+
libInstances.executeOnType('Api1', api => api.photoSelected(id));
59+
libInstances.executeOnType('Api2', (api) => { if (api !== this) api.displayNewPhoto(); });
4760
};
4861

4962
displayNewPhoto = () => {
@@ -57,81 +70,56 @@ class Api3 {
5770
};
5871

5972
setGrayscale = (grayscale) => {
60-
bridgedIframes.forEach((bridgedIframe) => {
61-
bridgedIframe.getApiOfType('Common').displayGrayscale(grayscale);
62-
});
73+
libInstances.executeOnType('Common', api => api.displayGrayscale(grayscale));
6374
};
6475

6576
setBlur = (blur) => {
66-
bridgedIframes.forEach((bridgedIframe) => {
67-
bridgedIframe.getApiOfType('Common').displayBlur(blur);
68-
});
77+
libInstances.executeOnType('Common', api => api.displayBlur(blur));
6978
};
7079
}
7180

7281
const apiMap = {
7382
Common, Api1, Api2, Api3,
7483
};
7584

76-
class BridgedIframe extends Component {
77-
setIframe = (iframe) => {
78-
if (!iframe || this.iframe) {
79-
return;
80-
}
81-
this.iframe = iframe;
82-
const { src, type, apis } = this.props;
85+
const BridgedIframe = ({
86+
src, type, apis, ...rest
87+
}) => {
88+
console.log(`render iframe: ${src}`);
8389

90+
const setIframe = (iframe) => {
8491
const url = new URL(src);
85-
this.webApiBridge = new WebApiBridge();
86-
this.webApiBridge.origin = url.origin;
87-
this.webApiBridge.targetOrigin = url.origin;
88-
this.send = this.webApiBridge.send.bind(this.webApiBridge);
89-
bridgedIframes.push(this);
90-
this.webApiBridge.target = iframe.contentWindow;
92+
const webApiBridge = new WebApiBridge();
93+
webApiBridge.origin = url.origin;
94+
webApiBridge.targetOrigin = url.origin;
95+
const send = webApiBridge.send.bind(webApiBridge);
96+
libInstances.add(createLibInstance({ webApiBridge, apis }));
97+
webApiBridge.target = iframe.contentWindow;
9198
window.addEventListener('message', (event) => {
92-
if (event && event.source === this.webApiBridge.target) {
93-
this.webApiBridge.onMessage(event, event.data);
99+
if (event && event.source === webApiBridge.target) {
100+
webApiBridge.onMessage(event, event.data);
94101
}
95102
});
96-
this.webApiBridge.apis = apis.map((apiClassName) => {
103+
webApiBridge.apis = apis.map((apiClassName) => {
97104
const api = new apiMap[apiClassName]();
98-
api.setSend(this.send);
105+
api.setSend(send);
99106
return api;
100107
});
101-
this.iframe.onload = () => {
108+
iframe.onload = () => {
102109
console.log(`${iframe.src} loaded`);
103-
this.send('ready', [{ type, apis }], false);
110+
send('ready', [{ type, apis }], false);
104111
};
105112
};
106113

107-
getApiOfType = apiName => (
108-
this.webApiBridge.apis.find(api => (
109-
api instanceof apiMap[apiName]
110-
))
114+
return (
115+
<iframe
116+
src={src}
117+
title={src}
118+
ref={(iframe) => { setIframe(iframe); }}
119+
scrolling="no"
120+
{...rest}
121+
/>
111122
);
112-
113-
render() {
114-
const {
115-
src, type, apis, ...rest
116-
} = this.props;
117-
console.log(`render iframe: ${src}`);
118-
119-
return (
120-
<iframe
121-
src={src}
122-
title={src}
123-
ref={(iframe) => { this.setIframe(iframe); }}
124-
scrolling="no"
125-
{...rest}
126-
/>
127-
);
128-
}
129-
}
130-
131-
BridgedIframe.propTypes = {
132-
src: string.isRequired,
133-
type: string.isRequired,
134-
apis: arrayOf(string).isRequired,
135123
};
136124

137125
export default BridgedIframe;

examples/webapp-library/webapp1/package.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616
"eject": "react-scripts eject"
1717
},
1818
"eslintConfig": {
19-
"extends": "react-app"
19+
"extends": "react-app",
20+
"plugins": [
21+
"react-hooks"
22+
],
23+
"rules": {
24+
"react-hooks/rules-of-hooks": "error",
25+
"react-hooks/exhaustive-deps": "warn"
26+
}
2027
},
2128
"browserslist": [
2229
">0.2%",
@@ -25,6 +32,7 @@
2532
"not op_mini all"
2633
],
2734
"devDependencies": {
28-
"cross-env": "^5.2.0"
35+
"cross-env": "^5.2.0",
36+
"eslint-plugin-react-hooks": "^1.6.0"
2937
}
3038
}
+13-30
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,26 @@
1-
import React, { Component } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import { startApis } from 'webapp-library';
33
import Type1 from './Type1';
44
import Type2 from './Type2';
55
import './App.css';
66

77
const parentOrigin = process.env.REACT_APP_PARENT_ORIGIN;
88

9-
class App extends Component {
10-
constructor(props) {
11-
super(props);
12-
this.state = {};
9+
const App = () => {
10+
console.log('webapp1 render');
11+
const [loadedType, setLoadedType] = useState();
12+
const [canModPhotos, setCanModPhotos] = useState();
1313

14+
useEffect(() => {
1415
startApis(parentOrigin).then(({ type, apis }) => {
15-
console.log('webapp1 startApis: ', type, apis);
16-
if (type === 'LibType1') this.setState({ loadedType: Type1 });
17-
else {
18-
this.setState({
19-
loadedType: Type2,
20-
canModPhotos: !!apis.find(api => api === 'Api3'),
21-
});
22-
}
16+
setCanModPhotos(!!apis.find(api => api === 'Api3'));
17+
setLoadedType(type);
2318
});
24-
}
19+
}, []);
2520

26-
render() {
27-
const { loadedType, canModPhotos } = this.state;
28-
if (!loadedType) return null;
29-
console.log(`webapp1 render ${loadedType.name}`);
30-
31-
return (
32-
<>
33-
{(loadedType === Type1) ? (
34-
<Type1 />
35-
) : (
36-
<Type2 canModPhotos={canModPhotos} />
37-
)}
38-
</>
39-
);
40-
}
41-
}
21+
if (!loadedType) return null;
22+
if (loadedType === 'LibType1') return <Type1 />;
23+
return <Type2 canModPhotos={canModPhotos} />;
24+
};
4225

4326
export default App;
+24-41
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,28 @@
1-
import React, { Component } from 'react';
1+
import React, { useEffect } from 'react';
22
import { common } from 'webapp-library/LibType2';
33
import { setCallback } from 'webapp-library/LibType1/Api1';
4-
import getPhoto from './getPhoto';
5-
6-
class Type1 extends Component {
7-
constructor(props) {
8-
super(props);
9-
10-
this.state = {};
11-
12-
setCallback('photoSelected', this.photoSelected);
13-
14-
window.onresize = () => {
15-
const { id } = this.state;
16-
this.photoSelected(id);
17-
};
18-
common.setCallback('displayGrayscale', (grayscale) => {
19-
const { id, blur } = this.state;
20-
getPhoto({ id, grayscale, blur }).then(photoInfo => this.setState(photoInfo));
21-
});
22-
common.setCallback('displayBlur', (blur) => {
23-
const { id, grayscale } = this.state;
24-
getPhoto({ id, grayscale, blur }).then(photoInfo => this.setState(photoInfo));
25-
});
26-
this.photoSelected(undefined);
27-
}
28-
29-
photoSelected = (id) => {
30-
const { grayscale, blur } = this.state;
31-
getPhoto({ id, grayscale, blur }).then(photoInfo => this.setState(photoInfo));
32-
};
33-
34-
render() {
35-
console.log('render webapp1.Type1');
36-
const { imageUrl } = this.state;
37-
if (!imageUrl) return null;
38-
39-
return (
40-
<img src={imageUrl} alt="" />
41-
);
42-
}
43-
}
4+
import usePicsum from './usePicsum';
5+
6+
7+
const Type1 = () => {
8+
console.log('render webapp1.Type1');
9+
const [photoInfo, setPhotoInfo] = usePicsum();
10+
const { url } = photoInfo;
11+
12+
useEffect(() => {
13+
setCallback('photoSelected', id => setPhotoInfo(pi => (
14+
{ ...pi, id }
15+
)));
16+
common.setCallback('displayGrayscale', displayGrayscale => setPhotoInfo(pi => (
17+
{ ...pi, grayscale: displayGrayscale }
18+
)));
19+
common.setCallback('displayBlur', displayBlur => setPhotoInfo(pi => (
20+
{ ...pi, blur: displayBlur }
21+
)));
22+
}, [setPhotoInfo]);
23+
24+
if (!url) return null;
25+
return <img src={url} alt="" />;
26+
};
4427

4528
export default Type1;

0 commit comments

Comments
 (0)