TanStack / router

🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
https://tanstack.com/router
MIT License
8.11k stars 630 forks source link

add virtual layout route when configuration is not found #1979

Open LoyalEnv0y opened 3 months ago

LoyalEnv0y commented 3 months ago

Describe the bug

I get an uncaught runtime error when I try to use the useSearch hook.

I was trying to move my state managed filters to search parameters so it would be easier for users to share filtered contents. To do this, I followed the instractions provided on the official Tanstack-Router guide.

Here is the code I have used to create my search parameters:

type TInstituteSearch = {
    page?: number;
    pageSize?: number;
    searchTerm?: string;
    sortColumn?: SortingState;
    filters?: TInstituteFilters;
};

export const Route = createFileRoute('/_authenticated/_institutes/institutes')({
    validateSearch: (search: Record<string, unknown>): TInstituteSearch => {
        return {
            page: search.page as number,
            pageSize: search.pageSize as number,
            searchTerm: search.searchTerm as string,
            sortColumn: search.sortColumn as SortingState,
            filters: search.filters as TInstituteFilters,
        };
    },
    beforeLoad: ({ context }) => {
        const user = context.AuthenticationContext.user;
        assignPermissions(user!, '/institutes');
    },
    component: Institutes,
});

Then I am simply trying to get the parameter values from the parameters. I have tried doing this in two ways:

First way:

import { getRouteApi } from '@tanstack/react-router';
const Route = getRouteApi('/_authenticated/_institutes/institutes');

const Institutes = () => {
        const { page, pageSize, searchTerm, sortColumn, filters } = Route.useSearch();
        return <div>......</div>
}

Second way:

import { useSearch } from '@tanstack/react-router';
const Institutes = () => {
        const { page, pageSize, searchTerm, sortColumn, filters } = useSearch({
        from: '/_authenticated/_institutes/institutes',
    });
        return <div>......</div>
}

Both of these methods causes the Cannot read properties of undefined (reading 'routeId') error:

image

The only way to not get this error is to no include the from property in the useSearch hook but they I get a typescript error saying Expected 1 arguments, but got 0.. I don't know what causes this.

Your Example Website or App

https://stackblitz.com/edit/github-fyoeqy?file=src%2Fpages%2FInstitute.tsx

Steps to Reproduce the Bug or Issue

Click on the institutes link in the navbar. In this example I get a different error which says that Invariant failed: Could not find an active match from "/_auth/_institutes/institutes" even though it exists on the routetree.

Here is the generated routeTree.gen.ts file on my local problem:

/* prettier-ignore-start */

/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file is auto-generated by TanStack Router

// Import Routes

import { Route as rootRoute } from './routes/__root'
import { Route as LoginImport } from './routes/login'
import { Route as ForgotPasswordImport } from './routes/forgotPassword'
import { Route as ErrorImport } from './routes/error'
import { Route as AuthenticatedImport } from './routes/_authenticated'
import { Route as AuthenticatedIndexImport } from './routes/_authenticated/index'
import { Route as AuthenticatedManagementChipTagTemplatesImport } from './routes/_authenticated/management/chipTagTemplates'
import { Route as AuthenticatedKeysProductKeysImport } from './routes/_authenticated/_keys/productKeys'
import { Route as AuthenticatedKeysIssuerKeysImport } from './routes/_authenticated/_keys/issuerKeys'
import { Route as AuthenticatedKeysInstituteKeysImport } from './routes/_authenticated/_keys/instituteKeys'
import { Route as AuthenticatedIssuersIssuersImport } from './routes/_authenticated/_issuers/issuers'
import { Route as AuthenticatedIssuersIssuerImport } from './routes/_authenticated/_issuers/issuer'
import { Route as AuthenticatedInstitutesInstitutionImport } from './routes/_authenticated/_institutes/institution'
import { Route as AuthenticatedInstitutesInstitutesImport } from './routes/_authenticated/_institutes/institutes'

// Create/Update Routes

const LoginRoute = LoginImport.update({
  path: '/login',
  getParentRoute: () => rootRoute,
} as any)

const ForgotPasswordRoute = ForgotPasswordImport.update({
  path: '/forgotPassword',
  getParentRoute: () => rootRoute,
} as any)

const ErrorRoute = ErrorImport.update({
  path: '/error',
  getParentRoute: () => rootRoute,
} as any)

const AuthenticatedRoute = AuthenticatedImport.update({
  id: '/_authenticated',
  getParentRoute: () => rootRoute,
} as any)

const AuthenticatedIndexRoute = AuthenticatedIndexImport.update({
  path: '/',
  getParentRoute: () => AuthenticatedRoute,
} as any)

const AuthenticatedManagementChipTagTemplatesRoute =
  AuthenticatedManagementChipTagTemplatesImport.update({
    path: '/management/chipTagTemplates',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedKeysProductKeysRoute =
  AuthenticatedKeysProductKeysImport.update({
    path: '/productKeys',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedKeysIssuerKeysRoute =
  AuthenticatedKeysIssuerKeysImport.update({
    path: '/issuerKeys',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedKeysInstituteKeysRoute =
  AuthenticatedKeysInstituteKeysImport.update({
    path: '/instituteKeys',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedIssuersIssuersRoute =
  AuthenticatedIssuersIssuersImport.update({
    path: '/issuers',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedIssuersIssuerRoute = AuthenticatedIssuersIssuerImport.update(
  {
    path: '/issuer',
    getParentRoute: () => AuthenticatedRoute,
  } as any,
)

const AuthenticatedInstitutesInstitutionRoute =
  AuthenticatedInstitutesInstitutionImport.update({
    path: '/institution',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

const AuthenticatedInstitutesInstitutesRoute =
  AuthenticatedInstitutesInstitutesImport.update({
    path: '/institutes',
    getParentRoute: () => AuthenticatedRoute,
  } as any)

// Populate the FileRoutesByPath interface

declare module '@tanstack/react-router' {
  interface FileRoutesByPath {
    '/_authenticated': {
      preLoaderRoute: typeof AuthenticatedImport
      parentRoute: typeof rootRoute
    }
    '/error': {
      preLoaderRoute: typeof ErrorImport
      parentRoute: typeof rootRoute
    }
    '/forgotPassword': {
      preLoaderRoute: typeof ForgotPasswordImport
      parentRoute: typeof rootRoute
    }
    '/login': {
      preLoaderRoute: typeof LoginImport
      parentRoute: typeof rootRoute
    }
    '/_authenticated/': {
      preLoaderRoute: typeof AuthenticatedIndexImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_institutes/institutes': {
      preLoaderRoute: typeof AuthenticatedInstitutesInstitutesImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_institutes/institution': {
      preLoaderRoute: typeof AuthenticatedInstitutesInstitutionImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_issuers/issuer': {
      preLoaderRoute: typeof AuthenticatedIssuersIssuerImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_issuers/issuers': {
      preLoaderRoute: typeof AuthenticatedIssuersIssuersImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_keys/instituteKeys': {
      preLoaderRoute: typeof AuthenticatedKeysInstituteKeysImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_keys/issuerKeys': {
      preLoaderRoute: typeof AuthenticatedKeysIssuerKeysImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/_keys/productKeys': {
      preLoaderRoute: typeof AuthenticatedKeysProductKeysImport
      parentRoute: typeof AuthenticatedImport
    }
    '/_authenticated/management/chipTagTemplates': {
      preLoaderRoute: typeof AuthenticatedManagementChipTagTemplatesImport
      parentRoute: typeof AuthenticatedImport
    }
  }
}

// Create and export the route tree

export const routeTree = rootRoute.addChildren([
  AuthenticatedRoute.addChildren([
    AuthenticatedIndexRoute,
    AuthenticatedInstitutesInstitutesRoute,
    AuthenticatedInstitutesInstitutionRoute,
    AuthenticatedIssuersIssuerRoute,
    AuthenticatedIssuersIssuersRoute,
    AuthenticatedKeysInstituteKeysRoute,
    AuthenticatedKeysIssuerKeysRoute,
    AuthenticatedKeysProductKeysRoute,
    AuthenticatedManagementChipTagTemplatesRoute,
  ]),
  ErrorRoute,
  ForgotPasswordRoute,
  LoginRoute,
])

/* prettier-ignore-end */

Expected behavior

Should route to the page without causing the runtime error.

Screenshots or Videos

No response

Platform

Additional context

This is my route definitions:

image

LoyalEnv0y commented 3 months ago

The only solution I have found so far is to set the strict property of the useSearch hook to false:

import { TInstituteSearchParams} from '../../types/institutes';

const { page, pageSize, searchTerm, sortColumn, filters } = useSearch({
        strict: false,
}) as TInstituteSearch;

I don't know if there is a better fix for this. From what I understand, it is caused by the nested /_authenticated/_institutes/instituterouting. When I try to specify the from property of the useSearch hook in a root route like /error, it works without an error.

SeanCassiere commented 3 months ago

When using a pathless/layout route, you need to have a configuration file for that route.

Here's the fixed reproduction (note, the _auth.tsx and _institutes.tsx files). https://stackblitz.com/edit/github-fyoeqy-jkyv4p?file=src%2Froutes%2Findex.tsx

SeanCassiere commented 3 months ago

Speaking with @schiller-manuel, we came to the conclusion that the router-generator should create a virtual layout route in the generated route tree.

For consideration, if a user has a layout route identifier with no critical or non-critical configuration files, we should probably console.warn the user that they are using the layout route setting without any actual configuration.

LoyalEnv0y commented 3 months ago

Thank you for your help Sean, the problem is definitely fixed on my side.

I've read the docs but I don't remember reading the layout routes requiring a config file. A warning in the console would help a lot to devs encountering the same problem in the future.