Skip to content

Commit 4d1d47b

Browse files
fix: implement Mercure hook
1 parent cfb979a commit 4d1d47b

File tree

5 files changed

+108
-24
lines changed

5 files changed

+108
-24
lines changed

templates/next/pages/foos/[id]/edit.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ const Page: NextComponentType<NextPageContext, Props, Props> = ({ {{{lc}}} }) =>
1818
<div>
1919
<div>
2020
<Head>
21-
<title>{{{{lc}}} && `Edit {{{ucf}}} ${{{~lc}}['@id']}`}</title>
21+
<title>{ {{{lc}}} && `Edit {{{ucf}}} ${ {{~lc}}['@id'] }` }</title>
2222
</Head>
2323
</div>
24-
<Form {{{lc}}}={{{{lc}}}} />
24+
<Form {{{lc}}}={ {{{lc}}} } />
2525
</div>
2626
);
2727
};
@@ -40,7 +40,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
4040
const response = await fetch("/{{{name}}}");
4141

4242
return {
43-
paths: response.data["hydra:member"].map(({{{lc}}}) => `${{{{lc}}}['@id']}/edit`),
43+
paths: response.data["hydra:member"].map(({{{lc}}}) => `${ {{~lc}}['@id'] }/edit`),
4444
fallback: true,
4545
};
4646
} catch (e) {

templates/next/pages/foos/[id]/index.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import { {{{ucf}}} } from "../../../types/{{{ucf}}}";
44
import { fetch } from "../../../utils/dataAccess";
55
import Head from "next/head";
66
import DefaultErrorPage from "next/error";
7+
import { useMercure } from "../../../utils/mercure";
78

89
interface Props {
910
{{{lc}}}: {{{ucf}}};
11+
hubURL: string;
1012
};
1113

12-
const Page: NextComponentType<NextPageContext, Props, Props> = ({ {{{lc}}} }) => {
14+
const Page: NextComponentType<NextPageContext, Props, Props> = (props) => {
15+
const {{{lc}}} = useMercure(props.{{{lc}}}, props.hubURL);
16+
1317
if (!{{{lc}}}) {
1418
return <DefaultErrorPage statusCode={404} />;
1519
}
@@ -18,20 +22,21 @@ const Page: NextComponentType<NextPageContext, Props, Props> = ({ {{{lc}}} }) =>
1822
<div>
1923
<div>
2024
<Head>
21-
<title>{`Show {{{ucf}}} ${{{~lc}}['@id']}`}</title>
25+
<title>{`Show {{{ucf}}} ${ {{~lc}}['@id'] }`}</title>
2226
</Head>
2327
</div>
24-
<Show {{{lc}}}={{{{lc}}}} />
28+
<Show {{{lc}}}={ {{{lc}}} } />
2529
</div>
2630
);
2731
};
2832

2933
export const getStaticProps: GetStaticProps = async ({ params }) => {
30-
const {{{lc}}} = await fetch(`/{{{name}}}/${params.id}`);
34+
const response = await fetch(`/{{{name}}}/${params.id}`);
3135

3236
return {
3337
props: {
34-
{{{lc}}}: {{{lc}}},
38+
{{{lc}}}: response.data,
39+
hubURL: response.hubURL,
3540
},
3641
revalidate: 1,
3742
}
@@ -42,7 +47,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
4247
const response = await fetch("/{{{name}}}");
4348

4449
return {
45-
paths: response.data["{{{hydraPrefix}}}member"].map(({{{lc}}}) => {{{lc}}}['@id']),
50+
paths: response.data["{{{hydraPrefix}}}member"].map(({{{lc}}}) => {{~lc}}['@id']),
4651
fallback: true,
4752
};
4853
} catch (e) {

templates/next/pages/foos/index.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,38 @@ import { {{{ucf}}} } from "../../types/{{{ucf}}}";
55
import { fetch } from "../../utils/dataAccess";
66
import Head from "next/head";
77
import Pagination from "../../components/common/Pagination";
8+
import { useMercure } from "../../utils/mercure";
89

910
interface Props {
1011
collection: PagedCollection<{{{ucf}}}>;
11-
};
12+
hubURL: string;
13+
}
1214

13-
const Page: NextComponentType<NextPageContext, Props, Props> = ({ collection }) => (
14-
<div>
15+
const Page: NextComponentType<NextPageContext, Props, Props> = (props) => {
16+
const collection = useMercure(props.collection, props.hubURL);
17+
18+
return (
1519
<div>
16-
<Head>
17-
<title>{{{ucf}}} List</title>
18-
</Head>
20+
<div>
21+
<Head>
22+
<title>{{{ucf}}} List</title>
23+
</Head>
24+
</div>
25+
<List {{{name}}}={collection["{{{hydraPrefix}}}member"]} />
26+
<Pagination collection={collection} />
1927
</div>
20-
<List {{{name}}}={collection['{{{hydraPrefix}}}member']} />
21-
<Pagination collection={collection} />
22-
</div>
23-
);
28+
);
29+
}
2430

2531
export const getServerSideProps: GetServerSideProps = async () => {
2632
const collection = await fetch('/{{{name}}}');
2733

28-
return {collection};
29-
};
34+
return {
35+
props: {
36+
collection: response.data,
37+
hubURL: response.hubURL,
38+
},
39+
}
40+
}
3041

3142
export default Page;

templates/next/utils/dataAccess.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ interface Violation {
1111
propertyPath: string;
1212
}
1313

14+
const extractHubURL = function (response) {
15+
const linkHeader = response.headers.get('Link');
16+
if (!linkHeader) return null;
17+
18+
const matches = linkHeader.match(
19+
/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/
20+
);
21+
22+
return matches && matches[1] ? new URL(matches[1], ENTRYPOINT) : null;
23+
}
24+
1425
export const fetch = async (id: string, init: RequestInit = {}) => {
1526
if (typeof init.headers === "undefined") init.headers = {};
1627
if (!init.headers.hasOwnProperty("Accept"))
@@ -26,10 +37,15 @@ export const fetch = async (id: string, init: RequestInit = {}) => {
2637
if (resp.status === 204) return;
2738

2839
const json = await resp.json();
29-
if (resp.ok) return normalize(json);
40+
if (resp.ok) {
41+
return {
42+
hubURL: extractHubURL(resp).toString(), // URL cannot be serialized as JSON, must be sent as string
43+
data: normalize(json),
44+
};
45+
}
3046

31-
const defaultErrorMsg = json["hydra:title"];
32-
const status = json["hydra:description"] || resp.statusText;
47+
const defaultErrorMsg = json["{{{hydraPrefix}}}title"];
48+
const status = json["{{{hydraPrefix}}}description"] || resp.statusText;
3349
if (!json.violations) throw Error(defaultErrorMsg);
3450
const fields = {};
3551
json.violations.map(

templates/next/utils/mercure.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import has from "lodash/has";
2+
import { PagedCollection } from "../types/Collection";
3+
import { normalize } from "./dataAccess";
4+
import { useEffect, useState } from "react";
5+
6+
const mercureSubscribe = (hubURL: string, data: any | PagedCollection<any>, setData: Function) => {
7+
const url = new URL(hubURL, window.origin);
8+
url.searchParams.append("topic", (new URL(data["@id"], window.origin)).toString());
9+
const eventSource = new EventSource(url.toString());
10+
eventSource.addEventListener("message", (event) => setData(normalize(JSON.parse(event.data))));
11+
12+
return eventSource;
13+
}
14+
15+
export const useMercure = (deps: any | PagedCollection<any>, hubURL: string) => {
16+
const [data, setData] = useState(deps);
17+
18+
useEffect(() => {
19+
setData(deps);
20+
}, [deps]);
21+
22+
if (!data) {
23+
return data;
24+
}
25+
26+
if (!has(data, "{{{hydraPrefix}}}member") && !has(data, "@id")) {
27+
console.error("Object sent is not in JSON-LD format.");
28+
29+
return data;
30+
}
31+
32+
useEffect(() => {
33+
if (has(data, "{{{hydraPrefix}}}member") && typeof data["{{{hydraPrefix}}}member"] !== "undefined" && data["{{{hydraPrefix}}}member"].length !== 0) {
34+
// It's a PagedCollection
35+
data["{{{hydraPrefix}}}member"].forEach((obj, pos) => mercureSubscribe(hubURL, obj, (datum) => {
36+
data["{{{hydraPrefix}}}member"][pos] = datum;
37+
setData(data);
38+
}));
39+
} else {
40+
// It's a single object
41+
const eventSource = mercureSubscribe(hubURL, data, setData);
42+
43+
return () => {
44+
eventSource.removeEventListener("message", setData);
45+
46+
return data;
47+
}
48+
}
49+
}, [data]);
50+
51+
return data;
52+
}

0 commit comments

Comments
 (0)