diff --git a/packages/tuono-router/src/components/Link.spec.tsx b/packages/tuono-router/src/components/Link.spec.tsx
index a8c3f6ca..19ac00b8 100644
--- a/packages/tuono-router/src/components/Link.spec.tsx
+++ b/packages/tuono-router/src/components/Link.spec.tsx
@@ -4,10 +4,14 @@ import { render, fireEvent, screen } from '@testing-library/react'
import Link from './Link'
const pushMock = vi.fn()
+const replaceMock = vi.fn()
const preloadMock = vi.fn()
vi.mock('../hooks/useRouter', () => ({
- useRouter: (): { push: typeof pushMock } => ({ push: pushMock }),
+ useRouter: (): { push: typeof pushMock; replace: typeof replaceMock } => ({
+ push: pushMock,
+ replace: replaceMock,
+ }),
}))
vi.mock('../hooks/useRoute', () => ({
@@ -51,6 +55,19 @@ describe('Link Component', () => {
expect(pushMock).toHaveBeenCalledWith('/test', { scroll: true })
})
+ it('calls router.replace on click when the replace prop is true', () => {
+ render(
+
+ Test Link
+ ,
+ )
+ const link = screen.getByRole('link')
+
+ fireEvent.click(link)
+ expect(replaceMock).toHaveBeenCalledWith('/test', { scroll: true })
+ expect(pushMock).not.toHaveBeenCalled()
+ })
+
it('does not navigate if href starts with "#"', () => {
render(Anchor Link)
const link = screen.getByRole('link')
diff --git a/packages/tuono-router/src/components/Link.tsx b/packages/tuono-router/src/components/Link.tsx
index a5890b1c..34b87691 100644
--- a/packages/tuono-router/src/components/Link.tsx
+++ b/packages/tuono-router/src/components/Link.tsx
@@ -16,6 +16,12 @@ interface TuonoLinkProps extends React.AnchorHTMLAttributes {
* @default true
*/
scroll?: boolean
+
+ /**
+ * If "true" the history entry will be replaced instead of pushed.
+ * @default false
+ */
+ replace?: boolean
}
function isEventModifierKeyActiveAndTargetDifferentFromSelf(
@@ -39,6 +45,7 @@ export default function Link(
scroll = true,
children,
href,
+ replace,
onClick,
...rest
} = componentProps
@@ -68,7 +75,9 @@ export default function Link(
event.preventDefault()
- router.push(href || '', { scroll })
+ const method = replace ? 'replace' : 'push'
+
+ router[method](href || '', { scroll })
}
return (
diff --git a/packages/tuono-router/src/hooks/useRouter.ts b/packages/tuono-router/src/hooks/useRouter.ts
index 1ec37f63..bd34af92 100644
--- a/packages/tuono-router/src/hooks/useRouter.ts
+++ b/packages/tuono-router/src/hooks/useRouter.ts
@@ -2,7 +2,10 @@ import React from 'react'
import { useRouterContext } from '../components/RouterContext'
-interface PushOptions {
+type NavigationType = 'pushState' | 'replaceState'
+type NavigationFn = (path: string, opts?: NavigationOptions) => void
+
+interface NavigationOptions {
/**
* If "false" the scroll offset will be kept across page navigation. Default "true"
*/
@@ -13,7 +16,13 @@ interface UseRouterResult {
/**
* Redirects to the path passed as argument updating the browser history.
*/
- push: (path: string, opt?: PushOptions) => void
+ push: NavigationFn
+
+ /**
+ * Redirects to the path passed as argument replacing the current history
+ * entry.
+ */
+ replace: NavigationFn
/**
* This object contains all the query params of the current route
@@ -29,9 +38,9 @@ interface UseRouterResult {
export const useRouter = (): UseRouterResult => {
const { location, updateLocation } = useRouterContext()
- const push = React.useCallback(
- (path: string, opt?: PushOptions): void => {
- const { scroll = true } = opt || {}
+ const navigate = React.useCallback(
+ (type: NavigationType, path: string, opts?: NavigationOptions): void => {
+ const { scroll = true } = opts || {}
const url = new URL(path, window.location.origin)
updateLocation({
@@ -41,7 +50,8 @@ export const useRouter = (): UseRouterResult => {
searchStr: url.search,
hash: url.hash,
})
- history.pushState(path, '', path)
+
+ history[type](path, '', path)
if (scroll) {
window.scroll(0, 0)
@@ -50,8 +60,23 @@ export const useRouter = (): UseRouterResult => {
[updateLocation],
)
+ const push = React.useCallback(
+ (path: string, opts?: NavigationOptions): void => {
+ navigate('pushState', path, opts)
+ },
+ [navigate],
+ )
+
+ const replace = React.useCallback(
+ (path: string, opts?: NavigationOptions): void => {
+ navigate('replaceState', path, opts)
+ },
+ [navigate],
+ )
+
return {
push,
+ replace,
query: location.search,
pathname: location.pathname,
}