Skip to content

Commit

Permalink
feat: cluster loads of markers
Browse files Browse the repository at this point in the history
* feat: use clustering library for more performant marker rendering

* fix: tidyup cluster impl

* feat: performance optimisation

* feat: ssg for static loo props gen

* feat: ssg using SchemaLink connecting to db

* fix: config
  • Loading branch information
ob6160 authored and Rupert Redington committed Nov 22, 2021
1 parent 0cf3266 commit 50f6a78
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 57 deletions.
18 changes: 18 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,21 @@ generates:
typesPath: ./graphql
plugins:
- graphql-codegen-apollo-next-ssr
src/api-client/staticPage.tsx:
config:
documentMode: external
importDocumentNodeExternallyFrom: ./graphql
reactApolloVersion: 3
withHooks: true
# withHOC: false
# excludePatterns: 'getComments'
# excludePatternsOptions: 'i'
# customDataIdFromObjectName: 'test'
# customDataIdFromObjectImport: 'abc'
apolloClientInstanceImport: '../components/withStaticApollo'
# apolloStateKey: '__APOLLO_STATE__'
preset: import-types
presetConfig:
typesPath: ./graphql
plugins:
- graphql-codegen-apollo-next-ssr
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"react-hook-form": "6.15.8",
"react-leaflet": "3.2.2",
"react-leaflet-control": "2.1.2",
"react-leaflet-markercluster": "3.0.0-rc1",
"react-tooltip": "4.2.21",
"resize-observer-polyfill": "1.5.1",
"styled-system": "5.1.5"
Expand Down
128 changes: 128 additions & 0 deletions src/api-client/staticPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as Types from './graphql';

import * as Operations from './graphql';
import { NextPage } from 'next';
import { NextRouter, useRouter } from 'next/router';
import { QueryHookOptions, useQuery } from '@apollo/client';
import * as Apollo from '@apollo/client';
import type React from 'react';
import { getApolloClient } from '../components/withStaticApollo';
export async function getServerPageFindLooById(
options: Omit<Apollo.QueryOptions<Types.FindLooByIdQueryVariables>, 'query'>,
ctx?: any
) {
const apolloClient = getApolloClient(ctx);

const data = await apolloClient.query<Types.FindLooByIdQuery>({
...options,
query: Operations.FindLooByIdDocument,
});

const apolloState = apolloClient.cache.extract();

return {
props: {
apolloState: apolloState,
data: data?.data,
error: data?.error ?? data?.errors ?? null,
},
};
}
export const useFindLooById = (
optionsFunc?: (
router: NextRouter
) => QueryHookOptions<Types.FindLooByIdQuery, Types.FindLooByIdQueryVariables>
) => {
const router = useRouter();
const options = optionsFunc ? optionsFunc(router) : {};
return useQuery(Operations.FindLooByIdDocument, options);
};
export type PageFindLooByIdComp = React.FC<{
data?: Types.FindLooByIdQuery;
error?: Apollo.ApolloError;
}>;
export const withPageFindLooById =
(
optionsFunc?: (
router: NextRouter
) => QueryHookOptions<
Types.FindLooByIdQuery,
Types.FindLooByIdQueryVariables
>
) =>
(WrappedComponent: PageFindLooByIdComp): NextPage =>
(props) => {
const router = useRouter();
const options = optionsFunc ? optionsFunc(router) : {};
const { data, error } = useQuery(Operations.FindLooByIdDocument, options);
return <WrappedComponent {...props} data={data} error={error} />;
};
export const ssrFindLooById = {
getServerPage: getServerPageFindLooById,
withPage: withPageFindLooById,
usePage: useFindLooById,
};
export async function getServerPageFindLoosNearby(
options: Omit<
Apollo.QueryOptions<Types.FindLoosNearbyQueryVariables>,
'query'
>,
ctx?: any
) {
const apolloClient = getApolloClient(ctx);

const data = await apolloClient.query<Types.FindLoosNearbyQuery>({
...options,
query: Operations.FindLoosNearbyDocument,
});

const apolloState = apolloClient.cache.extract();

return {
props: {
apolloState: apolloState,
data: data?.data,
error: data?.error ?? data?.errors ?? null,
},
};
}
export const useFindLoosNearby = (
optionsFunc?: (
router: NextRouter
) => QueryHookOptions<
Types.FindLoosNearbyQuery,
Types.FindLoosNearbyQueryVariables
>
) => {
const router = useRouter();
const options = optionsFunc ? optionsFunc(router) : {};
return useQuery(Operations.FindLoosNearbyDocument, options);
};
export type PageFindLoosNearbyComp = React.FC<{
data?: Types.FindLoosNearbyQuery;
error?: Apollo.ApolloError;
}>;
export const withPageFindLoosNearby =
(
optionsFunc?: (
router: NextRouter
) => QueryHookOptions<
Types.FindLoosNearbyQuery,
Types.FindLoosNearbyQueryVariables
>
) =>
(WrappedComponent: PageFindLoosNearbyComp): NextPage =>
(props) => {
const router = useRouter();
const options = optionsFunc ? optionsFunc(router) : {};
const { data, error } = useQuery(
Operations.FindLoosNearbyDocument,
options
);
return <WrappedComponent {...props} data={data} error={error} />;
};
export const ssrFindLoosNearby = {
getServerPage: getServerPageFindLoosNearby,
withPage: withPageFindLoosNearby,
usePage: useFindLoosNearby,
};
3 changes: 2 additions & 1 deletion src/components/LooMap/LooMap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useRouter } from 'next/router';
import { MapContainer, TileLayer, ZoomControl } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import 'react-leaflet-markercluster/dist/styles.min.css';
import { css } from '@emotion/react';
import Box from '../Box';
import { Media } from '../Media';
Expand All @@ -26,7 +27,7 @@ const LooMap: React.FC<Props> = ({
center,
zoom,
minZoom,
maxZoom,
maxZoom = 18,
staticMap = false,
}) => {
const router = useRouter();
Expand Down
100 changes: 55 additions & 45 deletions src/components/LooMap/Markers.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,78 @@
import { useMemo, useCallback } from 'react';
import React, {
useMemo,
useCallback,
useState,
useEffect,
useRef,
forwardRef,
} from 'react';
import { useRouter } from 'next/router';
import { useFindLoosNearbyQuery } from '../../api-client/graphql';
import { Marker, useMapEvents, useMap } from 'react-leaflet';
import ToiletMarkerIcon from './ToiletMarkerIcon';

import MarkerClusterGroup from 'react-leaflet-markercluster';
const KEY_ENTER = 13;

function mapToFindVars(map) {
return {
lat: map.getCenter().lat,
lng: map.getCenter().lng,
radius: Math.ceil(
map.getBounds().getNorthEast().distanceTo(map.getCenter())
),
};
}

const Markers = ({ focus }) => {
const router = useRouter();
const map = useMap();

const { data, refetch } = useFindLoosNearbyQuery({
variables: {
...mapToFindVars(map),
},
const [mapFind, _] = useState({
lat: 54.093409,
lng: -2.89479,
radius: 1000000,
});

useMapEvents({
moveend: () => refetch(mapToFindVars(map)),
const { data } = useFindLoosNearbyQuery({
fetchPolicy: 'cache-first',
nextFetchPolicy: 'cache-only',
variables: mapFind,
});

const memoizedMarkers = useMemo(() => {
if (!data?.loosByProximity) {
return null;
}
return data.loosByProximity.map((toilet) => (
<Marker
key={toilet.id}
position={toilet.location}
zIndexOffset={toilet.id === focus?.id ? 1000 : 0}
icon={
new ToiletMarkerIcon({
isHighlighted: toilet.id === focus?.id,
toiletId: toilet.id,
isUseOurLoosCampaign: toilet.campaignUOL,
})
}
alt={toilet.name || 'Unnamed toilet'}
eventHandlers={{
click: () => {
router.push(`/loos/${toilet.id}`);
},
keydown: (event: { originalEvent: { keyCode: number } }) => {
if (event.originalEvent.keyCode === KEY_ENTER) {
return data.loosByProximity.map((toilet) => {
return (
<Marker
key={toilet.id}
position={toilet.location}
zIndexOffset={toilet.id === focus?.id ? 1000 : 0}
icon={
new ToiletMarkerIcon({
isHighlighted: toilet.id === focus?.id,
toiletId: toilet.id,
isUseOurLoosCampaign: toilet.campaignUOL,
})
}
alt={toilet.name || 'Unnamed toilet'}
eventHandlers={{
click: () => {
router.push(`/loos/${toilet.id}`);
}
},
}}
keyboard={false}
/>
));
}, [data, router, focus]);
},
keydown: (event: { originalEvent: { keyCode: number } }) => {
if (event.originalEvent.keyCode === KEY_ENTER) {
router.push(`/loos/${toilet.id}`);
}
},
}}
keyboard={false}
/>
);
});
}, [data, focus, router]);

return <>{memoizedMarkers}</>;
return (
<MarkerClusterGroup
chunkedLoading={true}
animateAddingMarkers={false}
removeOutsideVisibleBounds={true}
maxClusterRadius={150}
>
{memoizedMarkers}
</MarkerClusterGroup>
);
};

export default Markers;
27 changes: 27 additions & 0 deletions src/components/withStaticApollo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextPage } from 'next';
import {
ApolloClient,
NormalizedCacheObject,
InMemoryCache,
ApolloProvider,
createHttpLink,
} from '@apollo/client';
import { SchemaLink } from '@apollo/client/link/schema';
import { schema } from '../pages/api';

export const withApollo = (Comp: NextPage) =>
function WithApollo(props: any) {
return (
<ApolloProvider client={getApolloClient()}>
<Comp />
</ApolloProvider>
);
};

export function getApolloClient(ctx: any): ApolloClient<NormalizedCacheObject> {
return new ApolloClient({
ssrMode: true,
link: new SchemaLink({ schema }),
cache: new InMemoryCache({}),
});
}
11 changes: 9 additions & 2 deletions src/pages/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import authDirective from '../../api/directives/authDirective';
import redactedDirective from '../../api/directives/redactedDirective';

import typeDefs from '../../api/typeDefs';
import { SchemaLink } from '@apollo/client/link/schema';
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from '@apollo/client';

const client = jwksClient({
jwksUri: `${process.env.AUTH0_ISSUER_BASE_URL}/.well-known/jwks.json`,
Expand All @@ -35,15 +41,16 @@ const { redactedDirectiveTypeDefs, redactedDirectiveTransformer } =
redactedDirective('redact');
const { authDirectiveTypeDefs, authDirectiveTransformer } =
authDirective('auth');
let schema = makeExecutableSchema({

export let schema = makeExecutableSchema({
typeDefs: [redactedDirectiveTypeDefs, authDirectiveTypeDefs, typeDefs],
resolvers,
});
schema = redactedDirectiveTransformer(schema);
schema = authDirectiveTransformer(schema);

// Add GraphQL API
const server = new ApolloServer({
export const server = new ApolloServer({
schema,
context: async ({ req, res }) => {
let user = null;
Expand Down
3 changes: 2 additions & 1 deletion src/pages/contact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Text from '../components/Text';
import Spacer from '../components/Spacer';

import config from '../config';
import { NextPage } from 'next';

const ContactPage = (props) => {
return (
Expand All @@ -34,4 +35,4 @@ const ContactPage = (props) => {
);
};

export default ContactPage;
export default ContactPage as NextPage;

0 comments on commit 50f6a78

Please sign in to comment.