Skip to main content

createMemoryRouter

Create a new data router that manages the application path using an in-memory History stack. Useful for non-browser environments without a DOM API, such as testing or React Native.

Function Signature

function createMemoryRouter(
  routes: RouteObject[],
  opts?: MemoryRouterOpts
): DataRouter

Route Configuration

The routes parameter accepts the same RouteObject[] configuration as createBrowserRouter. See the createBrowserRouter route configuration for complete details.
interface RouteObject {
  path?: string;
  caseSensitive?: boolean;
  index?: boolean;
  element?: React.ReactNode;
  Component?: React.ComponentType;
  loader?: LoaderFunction;
  action?: ActionFunction;
  errorElement?: React.ReactNode;
  ErrorBoundary?: React.ComponentType;
  children?: RouteObject[];
  // ... and more
}

Options Parameter

interface MemoryRouterOpts {
  // Base path for all routes
  basename?: string;
  
  // Context provider function for loaders/actions
  getContext?: () => RouterContextProvider;
  
  // Future flags for opt-in features
  future?: Partial<FutureConfig>;
  
  // Initial data from server-side rendering
  hydrationData?: HydrationState;
  
  // Initial entries in the history stack
  initialEntries?: InitialEntry[];
  
  // Index of initialEntries to start at (defaults to last entry)
  initialIndex?: number;
  
  // Instrumentation for observability
  unstable_instrumentations?: unstable_ClientInstrumentation[];
  
  // Custom data loading strategy
  dataStrategy?: DataStrategyFunction;
  
  // Lazy route discovery
  patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
}

Initial Entries

The initialEntries option allows you to specify the starting history stack:
type InitialEntry = string | Partial<Location>;
Each entry can be:
  • A string path: "/home", "/products/123", "/search?q=test"
  • A partial Location object:
    {
      pathname: "/products",
      search: "?category=electronics",
      hash: "#reviews",
      state: { from: "/home" }
    }
    

Return Type

Returns an initialized DataRouter instance to be passed to <RouterProvider>.

Examples

Basic Usage

import { createMemoryRouter, RouterProvider } from "react-router";

const router = createMemoryRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

With Initial Entries

const router = createMemoryRouter(
  [
    {
      path: "/",
      element: <Home />,
    },
    {
      path: "/products/:id",
      element: <Product />,
      loader: productLoader,
    },
  ],
  {
    initialEntries: ["/", "/products/123"],
    initialIndex: 1, // Start at /products/123
  }
);

Testing with History Stack

import { createMemoryRouter } from "react-router";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("navigates between pages", async () => {
  const router = createMemoryRouter(
    [
      {
        path: "/",
        element: (
          <div>
            <h1>Home</h1>
            <Link to="/about">About</Link>
          </div>
        ),
      },
      {
        path: "/about",
        element: <h1>About</h1>,
      },
    ],
    {
      initialEntries: ["/"],
    }
  );

  render(<RouterProvider router={router} />);
  
  expect(screen.getByText("Home")).toBeInTheDocument();
  
  await userEvent.click(screen.getByText("About"));
  
  expect(screen.getByText("About")).toBeInTheDocument();
});

Testing Loaders

import { createMemoryRouter } from "react-router";
import { render, screen, waitFor } from "@testing-library/react";

test("loads data", async () => {
  const mockLoader = jest.fn(async () => {
    return { user: { name: "John Doe" } };
  });

  const router = createMemoryRouter(
    [
      {
        path: "/",
        element: <UserProfile />,
        loader: mockLoader,
      },
    ],
    {
      initialEntries: ["/"],
    }
  );

  render(<RouterProvider router={router} />);
  
  await waitFor(() => {
    expect(screen.getByText("John Doe")).toBeInTheDocument();
  });
  
  expect(mockLoader).toHaveBeenCalledTimes(1);
});

Testing Error Boundaries

test("displays error boundary", async () => {
  const router = createMemoryRouter(
    [
      {
        path: "/",
        element: <div>App</div>,
        errorElement: <ErrorBoundary />,
        loader: async () => {
          throw new Error("Failed to load");
        },
      },
    ],
    {
      initialEntries: ["/"],
    }
  );

  render(<RouterProvider router={router} />);
  
  await waitFor(() => {
    expect(screen.getByText(/Failed to load/)).toBeInTheDocument();
  });
});

function ErrorBoundary() {
  const error = useRouteError();
  return <div>Error: {error.message}</div>;
}

Testing Actions

import { createMemoryRouter } from "react-router";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("submits form", async () => {
  const mockAction = jest.fn(async ({ request }) => {
    const formData = await request.formData();
    return { email: formData.get("email") };
  });

  const router = createMemoryRouter(
    [
      {
        path: "/contact",
        element: <ContactForm />,
        action: mockAction,
      },
    ],
    {
      initialEntries: ["/contact"],
    }
  );

  render(<RouterProvider router={router} />);
  
  await userEvent.type(
    screen.getByLabelText("Email"),
    "[email protected]"
  );
  await userEvent.click(screen.getByText("Submit"));
  
  await waitFor(() => {
    expect(mockAction).toHaveBeenCalled();
  });
});

With Complex Initial State

const router = createMemoryRouter(
  [
    {
      path: "/",
      element: <Home />,
    },
    {
      path: "/products/:id",
      element: <Product />,
    },
  ],
  {
    initialEntries: [
      "/",
      {
        pathname: "/products/123",
        search: "?variant=blue",
        state: { from: "/home" },
      },
    ],
    initialIndex: 1,
  }
);

Integration Test Example

import { createMemoryRouter, RouterProvider } from "react-router";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

describe("App Navigation", () => {
  it("navigates through the app flow", async () => {
    const router = createMemoryRouter(
      [
        {
          path: "/",
          element: <Layout />,
          children: [
            {
              index: true,
              element: <Home />,
            },
            {
              path: "login",
              element: <Login />,
              action: loginAction,
            },
            {
              path: "dashboard",
              element: <Dashboard />,
              loader: dashboardLoader,
            },
          ],
        },
      ],
      {
        initialEntries: ["/"],
      }
    );

    render(<RouterProvider router={router} />);
    
    // Start at home
    expect(screen.getByText("Welcome")).toBeInTheDocument();
    
    // Navigate to login
    await userEvent.click(screen.getByText("Login"));
    expect(screen.getByLabelText("Username")).toBeInTheDocument();
    
    // Submit login form
    await userEvent.type(screen.getByLabelText("Username"), "john");
    await userEvent.type(screen.getByLabelText("Password"), "secret");
    await userEvent.click(screen.getByText("Submit"));
    
    // Should navigate to dashboard
    await waitFor(() => {
      expect(screen.getByText("Dashboard")).toBeInTheDocument();
    });
  });
});

React Native Example

import { createMemoryRouter, RouterProvider } from "react-router";
import { View, Text } from "react-native";

const router = createMemoryRouter([
  {
    path: "/",
    element: (
      <View>
        <Text>Home Screen</Text>
      </View>
    ),
  },
  {
    path: "/profile/:id",
    element: <ProfileScreen />,
    loader: async ({ params }) => {
      return await fetchProfile(params.id);
    },
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Use Cases

When to Use createMemoryRouter

Use createMemoryRouter when:
  • Testing: Unit or integration testing React Router applications
  • React Native: Building mobile apps with React Native
  • Electron: Desktop applications without browser history
  • Node.js: Server-side rendering or other Node environments
  • Storybook: Documenting components that use routing
  • Isolated environments: Any non-browser environment needing routing

Advantages

  • No URL side effects: Perfect for testing since it doesn’t modify the browser URL
  • Full control: Complete control over the history stack via initialEntries
  • Deterministic: Predictable behavior for testing
  • Platform agnostic: Works in any JavaScript environment
  • All data router features: Full support for loaders, actions, and other data APIs

Testing Benefits

  1. Isolation: Tests don’t interfere with each other via shared browser history
  2. Speed: No browser navigation overhead
  3. Determinism: Start each test with a known history state
  4. Flexibility: Test specific history scenarios easily

Differences from Other Routers

FeaturecreateMemoryRoutercreateBrowserRoutercreateHashRouter
URL Updates✗ No✓ Yes✓ Yes (hash)
Browser History✗ No✓ Yes✓ Yes
Server ConfigNot neededRequiredNot needed
Testing✓ Perfect✗ Not ideal✗ Not ideal
Production Use✗ Rare✓ Recommended✓ Fallback
React Native✓ Yes✗ No✗ No

Common Testing Patterns

Setup Helper

function createTestRouter(routes: RouteObject[], options = {}) {
  return createMemoryRouter(routes, {
    initialEntries: ["/"],
    ...options,
  });
}

// Usage
test("my test", () => {
  const router = createTestRouter([
    { path: "/", element: <Home /> },
  ]);
  
  render(<RouterProvider router={router} />);
  // ... assertions
});

Custom Render Helper

function renderWithRouter(
  ui: React.ReactElement,
  {
    routes = [],
    initialEntries = ["/"],
    ...options
  } = {}
) {
  const router = createMemoryRouter(
    [
      {
        path: "*",
        element: ui,
      },
      ...routes,
    ],
    {
      initialEntries,
      ...options,
    }
  );
  
  return {
    ...render(<RouterProvider router={router} />),
    router,
  };
}

// Usage
test("renders component", () => {
  const { getByText } = renderWithRouter(<MyComponent />, {
    initialEntries: ["/test"],
  });
  
  expect(getByText("Hello")).toBeInTheDocument();
});

Build docs developers (and LLMs) love