diff --git a/.gitignore b/.gitignore index f4f072f2..f0dbfb17 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ package-lock.json nextjs.d.ts historyjs.d.ts +static.d.ts diff --git a/esm-postbuild.sh b/esm-postbuild.sh index 4cbd4f1c..c4fa859a 100755 --- a/esm-postbuild.sh +++ b/esm-postbuild.sh @@ -6,3 +6,4 @@ cat >build/cjs/package.json <', () => { + const { useQuery } = factoryParameters( + { + someParameter: pm('parameter', serializers.string), + }, + { someParameter: 'test' } + ) + const ComponentThatRendersSomethingStatically = () => { + const { + pushState, + replaceState, + values: { someParameter }, + } = useQuery() + return ( + <> + {someParameter} + + + + + ) + } + + it('should render a static search string', () => { + render( + + + + ) + expect(screen.getByRole('content').textContent).toEqual('myValue') + }) + + it('should support replace and push state', async () => { + render( + + + + ) + 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') + }) +}) diff --git a/src/lib/adapters/nextjs/index.tsx b/src/lib/adapters/nextjs/index.tsx index fdc43fa1..0723ed2f 100644 --- a/src/lib/adapters/nextjs/index.tsx +++ b/src/lib/adapters/nextjs/index.tsx @@ -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('?') || [] @@ -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, @@ -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 || ''}` @@ -71,30 +63,21 @@ export const GeschichteForNextjs: FC = ({ 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, @@ -102,19 +85,19 @@ export const GeschichteForNextjs: FC = ({ 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, @@ -123,20 +106,20 @@ export const GeschichteForNextjs: FC = ({ 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), @@ -155,8 +138,8 @@ export const GeschichteForNextjs: FC = ({ 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) => { @@ -173,7 +156,7 @@ export const GeschichteForNextjs: FC = ({ return () => { Router.events.off('beforeHistoryChange', routeChangeStartHandler) } - }, [updateFromQuery, clientSideHref, clientSideSearch]) + }, [updateFromQuery]) useEffect(() => { const { unregister } = state @@ -192,12 +175,10 @@ type ClientOnlyProps = Pick< | 'defaultReplaceOptions' | 'routerPush' | 'routerReplace' - | 'routerAsPath' - | 'routerQuery' > & { readonly children: ReactNode readonly omitQueries?: boolean -} & Partial> +} // 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 @@ -207,8 +188,6 @@ export const GeschichteForNextjsWrapper: FC = ({ omitQueries = true, defaultPushOptions = defaultRoutingOptions, defaultReplaceOptions = defaultRoutingOptions, - clientSideSearch = defaultClientSideSearch, - clientSideHref = defaultClientSideHref, ...props }) => { const { asPath } = useRouter() @@ -235,8 +214,6 @@ export const GeschichteForNextjsWrapper: FC = ({ initialClientOnlyAsPath={thisAsPath} defaultReplaceOptions={defaultReplaceOptions} defaultPushOptions={defaultPushOptions} - clientSideHref={clientSideHref} - clientSideSearch={clientSideSearch} asPath={pp} {...props} /> diff --git a/src/lib/adapters/static/index.tsx b/src/lib/adapters/static/index.tsx new file mode 100644 index 00000000..fe9ffa11 --- /dev/null +++ b/src/lib/adapters/static/index.tsx @@ -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) => { + const historyInstance: HistoryManagement = useMemo(() => { + return { + initialSearch: () => search || '?', + push: async () => { + return + }, + replace: async () => { + return + }, + } + }, [search]) + + const value = useMemo(() => useGeschichte(historyInstance), [historyInstance]) + return {children} +} + +export default memo(StaticGeschichteProvider)