Skip to content

Commit

Permalink
feat: added static renderer instead of allowing Next.js specific over…
Browse files Browse the repository at this point in the history
…writes

BREAKING CHANGE: Reverted next.js changes for static rendering
  • Loading branch information
BowlingX committed Mar 15, 2023
1 parent b89eb6b commit 2d7c731
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ package-lock.json

nextjs.d.ts
historyjs.d.ts
static.d.ts
1 change: 1 addition & 0 deletions esm-postbuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ cat >build/cjs/package.json <<!EOF

cp ./build/mjs/lib/adapters/nextjs/index.d.ts nextjs.d.ts
cp ./build/mjs/lib/adapters/historyjs/index.d.ts historyjs.d.ts
cp ./build/mjs/lib/adapters/static/index.d.ts static.d.ts
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"import": "./build/mjs/lib/adapters/historyjs/index.js",
"require": "./build/cjs/lib/adapters/historyjs/index.js"
},
"./static": {
"types": "./build/mjs/lib/adapters/static/index.d.ts",
"import": "./build/mjs/lib/adapters/static/index.js",
"require": "./build/cjs/lib/adapters/static/index.js"
},
"./package.json": "./package.json"
},
"files": [
Expand Down
76 changes: 76 additions & 0 deletions src/__tests__/static-render.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* tslint:disable:no-expression-statement no-object-mutation */

import { render, cleanup, screen, act } from '@testing-library/react'
import userEventImport from '@testing-library/user-event'
import React from 'react'
import Geschichte from '../lib/adapters/static/index.js'
import { factoryParameters } from '../lib/store.js'
import { pm } from '../lib/utils.js'
import { serializers } from '../lib/serializers.js'

const userEvent = userEventImport.default || userEventImport

afterEach(cleanup)

describe('<StaticGeschichteProvider />', () => {
const { useQuery } = factoryParameters(
{
someParameter: pm('parameter', serializers.string),
},
{ someParameter: 'test' }
)
const ComponentThatRendersSomethingStatically = () => {
const {
pushState,
replaceState,
values: { someParameter },
} = useQuery()
return (
<>
<span role="content">{someParameter}</span>
<button
role="push"
onClick={() =>
void pushState(
(state) => void (state.someParameter = 'nextPushState')
)
}
></button>

<button
role="replace"
onClick={() =>
void replaceState(
(state) => void (state.someParameter = 'nextReplaceState')
)
}
></button>
</>
)
}

it('should render a static search string', () => {
render(
<Geschichte search="?parameter=myValue">
<ComponentThatRendersSomethingStatically />
</Geschichte>
)
expect(screen.getByRole('content').textContent).toEqual('myValue')
})

it('should support replace and push state', async () => {
render(
<Geschichte>
<ComponentThatRendersSomethingStatically />
</Geschichte>
)
await act(async () => {
await userEvent.click(screen.getByRole('push'))
})
expect(screen.getByRole('content').textContent).toEqual('nextPushState')
await act(async () => {
await userEvent.click(screen.getByRole('replace'))
})
expect(screen.getByRole('content').textContent).toEqual('nextReplaceState')
})
})
47 changes: 12 additions & 35 deletions src/lib/adapters/nextjs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { shallow } from 'zustand/shallow'
import { StoreState } from '../../middleware.js'
import { HistoryManagement, StoreContext, useGeschichte } from '../../store.js'
import type { UrlObject } from 'url'
import type { ParsedUrlQuery } from 'querystring'

const split = (url?: string) => url?.split('?') || []

Expand All @@ -37,10 +36,6 @@ interface Props {
readonly defaultPushOptions?: TransitionOptions
readonly defaultReplaceOptions?: TransitionOptions
// tslint:disable-next-line:no-mixed-interface
readonly routerAsPath?: () => string
readonly routerQuery?: () => ParsedUrlQuery
readonly clientSideHref: () => string
readonly clientSideSearch: () => string
readonly routerPush?: (
url: Url,
as: UrlObject,
Expand All @@ -57,9 +52,6 @@ interface Props {
// FIXME: Somehow imports are messed up for nextjs when importing from modules (see https://github.com/vercel/next.js/issues/36794)
const Router = (NextRouter as any as { readonly default: Router$ }).default

const defaultClientSideHref = () => window.location.href
const defaultClientSideSearch = () => window.location.search

const queryFromPath = (path: string) => {
const [, query] = split(path)
return `?${query || ''}`
Expand All @@ -71,50 +63,41 @@ export const GeschichteForNextjs: FC<Props> = ({
initialClientOnlyAsPath,
defaultPushOptions,
defaultReplaceOptions,
routerAsPath,
routerQuery,
routerPush,
routerReplace,
clientSideHref,
clientSideSearch,
}) => {
const lastClientSideQuery = useRef(initialClientOnlyAsPath)

const historyInstance: HistoryManagement = useMemo(() => {
const thisRouterAsPath = () =>
routerAsPath ? routerAsPath() : Router.asPath
const thisRouterQuery = () => (routerQuery ? routerQuery() : Router.query)

return {
initialSearch: () => {
const [, query] =
typeof window === 'undefined'
? split(asPath)
: split(lastClientSideQuery.current || thisRouterAsPath())
: split(lastClientSideQuery.current || Router.asPath)
return `?${query || ''}`
},
push: (query, options) => {
const [pathname] = split(thisRouterAsPath())
const [pathname] = split(Router.asPath)
const routerOptions = {
...defaultPushOptions,
...options,
}

if (routerPush) {
return routerPush(
{ pathname, query: thisRouterQuery() },
{ pathname, query: Router.query },
{ pathname, query },
routerOptions
)
}
return Router.push(
{ pathname, query: thisRouterQuery() },
{ pathname, query: Router.query },
{ pathname, query },
routerOptions
)
},
replace: (query, options) => {
const [pathname] = split(thisRouterAsPath())
const [pathname] = split(Router.asPath)

const routerOptions = {
...defaultReplaceOptions,
Expand All @@ -123,20 +106,20 @@ export const GeschichteForNextjs: FC<Props> = ({

if (routerReplace) {
return routerReplace(
{ pathname, query: thisRouterQuery() },
{ pathname, query: Router.query },
{ pathname, query },
routerOptions
)
}
return Router.replace(
{ pathname, query: thisRouterQuery() },
{ pathname, query: Router.query },
{ pathname, query },
routerOptions
)
},
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [routerPush, routerReplace, routerQuery, routerAsPath])
}, [routerPush, routerReplace])

const useStore = useMemo(
() => useGeschichte(historyInstance),
Expand All @@ -155,8 +138,8 @@ export const GeschichteForNextjs: FC<Props> = ({

useEffect(() => {
// tslint:disable-next-line
lastClientSideQuery.current = clientSideHref()
updateFromQuery(clientSideSearch())
lastClientSideQuery.current = window.location.href
updateFromQuery(window.location.search)
// tslint:disable-next-line:no-let
let skipEvent = true
const routeChangeStartHandler = (path: string) => {
Expand All @@ -173,7 +156,7 @@ export const GeschichteForNextjs: FC<Props> = ({
return () => {
Router.events.off('beforeHistoryChange', routeChangeStartHandler)
}
}, [updateFromQuery, clientSideHref, clientSideSearch])
}, [updateFromQuery])

useEffect(() => {
const { unregister } = state
Expand All @@ -192,12 +175,10 @@ type ClientOnlyProps = Pick<
| 'defaultReplaceOptions'
| 'routerPush'
| 'routerReplace'
| 'routerAsPath'
| 'routerQuery'
> & {
readonly children: ReactNode
readonly omitQueries?: boolean
} & Partial<Pick<Props, 'clientSideHref' | 'clientSideSearch'>>
}

// see https://nextjs.org/docs/api-reference/next/router#routerpush for options;
// in general we want shallow (see https://nextjs.org/docs/routing/shallow-routing) routing in most cases and not scroll
Expand All @@ -207,8 +188,6 @@ export const GeschichteForNextjsWrapper: FC<ClientOnlyProps> = ({
omitQueries = true,
defaultPushOptions = defaultRoutingOptions,
defaultReplaceOptions = defaultRoutingOptions,
clientSideSearch = defaultClientSideSearch,
clientSideHref = defaultClientSideHref,
...props
}) => {
const { asPath } = useRouter()
Expand All @@ -235,8 +214,6 @@ export const GeschichteForNextjsWrapper: FC<ClientOnlyProps> = ({
initialClientOnlyAsPath={thisAsPath}
defaultReplaceOptions={defaultReplaceOptions}
defaultPushOptions={defaultPushOptions}
clientSideHref={clientSideHref}
clientSideSearch={clientSideSearch}
asPath={pp}
{...props}
/>
Expand Down
28 changes: 28 additions & 0 deletions src/lib/adapters/static/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { memo, useMemo } from 'react'
import { HistoryManagement, StoreContext, useGeschichte } from '../../store.js'

interface Props {
readonly search?: string
}

const StaticGeschichteProvider = ({
search,
children,
}: React.PropsWithChildren<Props>) => {
const historyInstance: HistoryManagement = useMemo(() => {
return {
initialSearch: () => search || '?',
push: async () => {
return
},
replace: async () => {
return
},
}
}, [search])

const value = useMemo(() => useGeschichte(historyInstance), [historyInstance])
return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>
}

export default memo(StaticGeschichteProvider)

0 comments on commit 2d7c731

Please sign in to comment.