Skip to content
This repository was archived by the owner on Mar 27, 2018. It is now read-only.

Commit 1845a4c

Browse files
author
Ian Wensink
committed
fix(entity-mapper): refactor entity mapper
* Deprecate everything that was called 'paragraph' and replace with 'entity' * Support dynamic imports in the Entity Mapper (previously called Paragraph) * Remove spreading of entity fields into Entity component *
1 parent ab08dff commit 1845a4c

File tree

8 files changed

+261
-193
lines changed

8 files changed

+261
-193
lines changed

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"babel-runtime": "^6.25.0",
1616
"get-nested": "^4.0.0",
1717
"prop-types": "^15.5.10",
18-
"react-async-bootstrapper": "^1.1.2"
18+
"react-async-bootstrapper": "^1.1.2",
19+
"react-is-deprecated": "^0.1.2"
1920
},
2021
"nyc": {
2122
"extension": [
@@ -30,7 +31,7 @@
3031
]
3132
},
3233
"peerDependencies": {
33-
"hn": "^0.3.3",
34+
"hn": "^0.6.1",
3435
"next": "^3.0.6",
3536
"react": "^16.0.0"
3637
},
@@ -40,7 +41,7 @@
4041
"babel-preset-env": "^1.6.0",
4142
"babel-preset-react": "^6.24.1",
4243
"babel-register": "^6.24.1",
43-
"hn": "^0.4.0",
44+
"hn": "^0.6.1",
4445
"next": "^3.0.6",
4546
"react": "^16.0.0",
4647
"react-dom": "^16.0.0",

src/DrupalPage/index.js

Lines changed: 47 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,21 @@
1-
import React, { Component } from 'react';
1+
import React, { Fragment, Component } from 'react';
22
import { parse } from 'url';
33
import PropTypes from 'prop-types';
44
import getNested from 'get-nested';
55
import site from '../site';
6+
import EntityMapper from '../EntityMapper';
67

7-
const components = new Map();
8-
9-
export default class DrupalPage extends Component {
10-
static propTypes = {
11-
url: PropTypes.string.isRequired,
12-
layout: PropTypes.oneOfType([
13-
PropTypes.func,
14-
PropTypes.string,
15-
]),
16-
mapper: PropTypes.oneOfType([
17-
PropTypes.shape(),
18-
PropTypes.func,
19-
]).isRequired,
20-
asyncMapper: PropTypes.bool,
21-
layoutProps: PropTypes.shape(),
22-
renderWhileLoadingData: PropTypes.bool,
23-
pageProps: PropTypes.shape(),
24-
};
25-
26-
static defaultProps = {
27-
layout: 'div',
28-
asyncMapper: false,
29-
layoutProps: {},
30-
renderWhileLoadingData: false,
31-
pageProps: undefined,
32-
};
33-
8+
class DrupalPage extends Component {
349
static contextTypes = {
3510
hnContext: PropTypes.object,
3611
};
3712

38-
constructor(props) {
39-
super(props);
40-
41-
this.state = {
42-
ready: false,
43-
loadingData: true,
44-
dataUrl: null,
45-
pageUuid: null,
46-
contentTypeComponentSymbol: null,
47-
};
48-
}
13+
state = {
14+
ready: false,
15+
loadingData: true,
16+
dataUrl: null,
17+
pageUuid: null,
18+
};
4919

5020
/**
5121
* If this component exists in a tree that is invoked with the waitForHnData function, this function is invoked.
@@ -85,7 +55,7 @@ export default class DrupalPage extends Component {
8555
* @param url
8656
* @param mapper
8757
* @param asyncMapper
88-
* @returns {Promise.<{pageUuid: void, contentTypeComponentSymbol: Symbol}>}
58+
* @returns {Promise.<{pageUuid: string}>}
8959
*/
9060
static async assureData({ url, mapper, asyncMapper }) {
9161
// Get the page. If the page was already fetched before, this should be instant.
@@ -94,40 +64,7 @@ export default class DrupalPage extends Component {
9464
throw Error('An error occurred getting a response from the server.');
9565
}
9666

97-
// This gets the data from the site, based on the uuid.
98-
const data = site.getData(pageUuid);
99-
100-
// This should give back a bundle string, that is used in the mapper.
101-
const bundle = getNested(() => `${data.__hn.entity.type}__${data.__hn.entity.bundle}`, '_fallback');
102-
103-
// Get the component that belongs to this content type
104-
let contentTypeComponent = typeof mapper === 'function' ? mapper(data, bundle) : mapper[bundle];
105-
106-
// If asyncMapper is true, execute the function so it returns a promise.
107-
if(asyncMapper && typeof contentTypeComponent === 'function') {
108-
contentTypeComponent = contentTypeComponent();
109-
}
110-
111-
// If a promise was returned, resolve it.
112-
if(contentTypeComponent && typeof contentTypeComponent.then !== 'undefined') {
113-
contentTypeComponent = await contentTypeComponent;
114-
}
115-
116-
// Make sure there is a contentComponent.
117-
if(!contentTypeComponent) {
118-
throw Error('No content type found');
119-
}
120-
121-
// If it has a .default (ES6+), use that.
122-
if(contentTypeComponent.default) {
123-
contentTypeComponent = contentTypeComponent.default;
124-
}
125-
126-
// Store the contentTypeComponent globally, so it can be rendered sync.
127-
const contentTypeComponentSymbol = Symbol.for(contentTypeComponent);
128-
components.set(contentTypeComponentSymbol, contentTypeComponent);
129-
130-
return { pageUuid, contentTypeComponentSymbol };
67+
return { pageUuid };
13168
}
13269

13370
componentWillUnmount() {
@@ -144,31 +81,26 @@ export default class DrupalPage extends Component {
14481

14582
this.setState({ loadingData: true });
14683

147-
if(!this.props.renderWhileLoadingData) {
148-
// Mark this component as not-ready. This unmounts the Layout and old ContentType.
149-
this.setState({ ready: false });
150-
}
151-
15284
// Load the data.
153-
const { pageUuid, contentTypeComponentSymbol } = await DrupalPage.assureData({ url, mapper, asyncMapper});
85+
const { pageUuid } = await DrupalPage.assureData({ url, mapper, asyncMapper});
15486

15587
// Check if this is still the last request.
15688
if(this.lastRequest !== lastRequest) return;
15789

15890
// Mark this component as ready. This mounts the Layout and new ContentType.
159-
this.setState({ pageUuid, contentTypeComponentSymbol, ready: true, loadingData: false, dataUrl: url });
91+
this.setState({ pageUuid, loadingData: false, dataUrl: url });
16092
}
16193

16294
render() {
95+
// Mark this component as not-ready. This unmounts the Layout and old ContentType.
16396
// Only render if the component is ready.
164-
if (!this.state.ready) return null;
97+
if (this.entity && !this.props.renderWhileLoadingData && !this.entity.isReady()) return null;
16598

16699
// Get props.
167100
const Layout = this.props.layout;
168101

169102
// Get the data and content types with the state properties.
170103
const data = site.getData(this.state.pageUuid);
171-
const ContentType = components.get(this.state.contentTypeComponentSymbol);
172104

173105
return (
174106
<Layout
@@ -177,12 +109,41 @@ export default class DrupalPage extends Component {
177109
page={data}
178110
{...this.props.layoutProps}
179111
>
180-
<ContentType
112+
<EntityMapper
113+
mapper={this.props.mapper}
114+
uuid={this.state.pageUuid}
115+
asyncMapper={this.props.asyncMapper}
116+
entityProps={this.props.pageProps}
181117
page={data}
182-
{...data}
183-
{...this.props.pageProps}
118+
ref={(c) => { this.entity = c; }}
184119
/>
185120
</Layout>
186121
);
187122
}
188123
}
124+
125+
DrupalPage.propTypes = {
126+
url: PropTypes.string.isRequired,
127+
layout: PropTypes.oneOfType([
128+
PropTypes.func,
129+
PropTypes.string,
130+
]),
131+
mapper: PropTypes.oneOfType([
132+
PropTypes.shape(),
133+
PropTypes.func,
134+
]).isRequired,
135+
asyncMapper: PropTypes.bool,
136+
layoutProps: PropTypes.shape(),
137+
renderWhileLoadingData: PropTypes.bool,
138+
pageProps: PropTypes.shape(),
139+
};
140+
141+
DrupalPage.defaultProps = {
142+
layout: Fragment,
143+
asyncMapper: undefined,
144+
layoutProps: {},
145+
renderWhileLoadingData: false,
146+
pageProps: undefined,
147+
};
148+
149+
export default DrupalPage;

src/EntityListMapper.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { Fragment } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { deprecate } from 'react-is-deprecated';
4+
import Entity from './EntityMapper';
5+
6+
const EntityListMapper = ({ mapper, paragraphs, entities, page, Wrapper, entityWrapper, paragraphProps, entityProps, asyncMapper }) =>
7+
(entities || paragraphs).map((ref, index) => {
8+
const EntityWrapper = entityWrapper || Wrapper || Fragment;
9+
const uuid = ref.target_uuid || ref;
10+
return (
11+
<EntityWrapper key={uuid}>
12+
<Entity
13+
uuid={uuid}
14+
index={index}
15+
mapper={mapper}
16+
asyncMapper={asyncMapper}
17+
entityProps={{ page, ...entityProps || paragraphProps }}
18+
/>
19+
</EntityWrapper>
20+
);
21+
});
22+
23+
EntityListMapper.propTypes = {
24+
mapper: PropTypes.oneOfType([
25+
PropTypes.shape(),
26+
PropTypes.func,
27+
]).isRequired,
28+
entities: PropTypes.arrayOf(PropTypes.oneOfType([
29+
PropTypes.shape({
30+
target_uuid: PropTypes.string.isRequired,
31+
}),
32+
PropTypes.string,
33+
])).isRequired,
34+
entityWrapper: PropTypes.element,
35+
entityProps: PropTypes.shape(),
36+
asyncMapper: PropTypes.bool,
37+
paragraphProps: deprecate(PropTypes.shape(), 'Warning: The prop "paragraphProps" is replaced by "entityProps".'),
38+
paragraphs: deprecate(PropTypes.arrayOf(PropTypes.shape()), 'Warning: The prop "paragraphs" is replaced by "entities".'),
39+
page: deprecate(PropTypes.shape(), 'Warning: The prop "page" is deprecated. Please use "entityProps={{ page }}" instead.'),
40+
Wrapper: deprecate(PropTypes.element, 'Warning: The prop "Wrapper" is replaced by "entityWrapper".'),
41+
};
42+
43+
EntityListMapper.defaultProps = {
44+
Wrapper: undefined,
45+
entityWrapper: undefined,
46+
paragraphProps: undefined,
47+
entityProps: undefined,
48+
asyncMapper: undefined,
49+
};
50+
51+
export default EntityListMapper;

0 commit comments

Comments
 (0)