Skip to content
Twilight React Playground
ثيم رائدaren

NavigationProvider

providerAdvancedlive demo

Supplies the function useNavigate() returns to everything below it; TwilightProvider mounts one for TanStack Router, and tests can mount their own.

import { NavigationProvider } from '@salla.sa/twilight-theme-engine/providers';

In plain words

useNavigate() does not know your router. NavigationProvider hands it the function that really changes the page, and TwilightProvider mounts one for you.

Mount your own to record navigation in a test, or to plug in a different router.

Signature

function NavigationProvider(props: { navigate: NavigateFn; children: ReactNode }): JSX.Element

Try it live

A NavigationProvider nested inside the page’s own one: every useNavigate() below it gets this navigate function instead.Try this: press the button: the call is recorded, and nothing navigates, because the nearest provider wins.
Storefront canvas · en · LTR
Calls the nested provider received (the page stays put):
none yet
Controls
replace
What a theme writes
import { render, screen, fireEvent } from '@testing-library/react';
import { NavigationProvider } from '@salla.sa/twilight-theme-engine/providers';
import { CheckoutButton } from './CheckoutButton';

it('navigates to /cart', () => {
  const navigate = vi.fn();
  render(
    <NavigationProvider navigate={navigate}>
      <CheckoutButton />
    </NavigationProvider>
  );
  fireEvent.click(screen.getByRole('button'));
  expect(navigate).toHaveBeenCalledWith('/cart', { replace: false });
});

Example

tests/CheckoutButton.test.tsx
import { fireEvent, render, screen } from '@testing-library/react';
import { NavigationProvider } from '@salla.sa/twilight-theme-engine/providers';
import { CheckoutButton } from '../app/components/CheckoutButton';

it('goes to the cart', () => {
  const navigate = vi.fn();
  render(
    <NavigationProvider navigate={navigate}>
      <CheckoutButton />
    </NavigationProvider>
  );
  fireEvent.click(screen.getByRole('button'));
  expect(navigate).toHaveBeenCalledWith('/cart');
});

How it behaves

  • The TanStack setup passes (to, opts) => navigate({ to: localizeDestination(to, locale), replace: opts?.replace }); that is where the automatic locale comes from.

  • It affects useNavigate() and NavigationInterceptor only. The engine Link renders through LinkProvider and ignores it.

  • With no provider, useNavigate() returns the default, which assigns to to window.location (or calls location.replace(to) with replace): a full page load.

  • The provider builds a new context value on every render, so its consumers render again whenever it does. Pass a stable function (useCallback) and keep the provider high in the tree.

Related

Source and docs