{"record":{"id":"80187fe152bcd290","repo":"ionic-team/ionic-framework","slug":"iontabs-must-contain-an-ionrouteroutlet-or-an-iont-80187f","errorCode":null,"errorMessage":"IonTabs must contain an IonRouterOutlet or an IonTab","messagePattern":"IonTabs must contain an IonRouterOutlet or an IonTab","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react/src/components/navigation/IonTabs.tsx","lineNumber":178,"sourceCode":"      if (onIonTabsDidChange !== undefined) {\n        childProps = {\n          ...childProps,\n          onIonTabsDidChange,\n        };\n      }\n\n      if (onIonTabsWillChange !== undefined) {\n        childProps = {\n          ...childProps,\n          onIonTabsWillChange,\n        };\n      }\n\n      this.ionTabContextState.tabBarProps = childProps;\n    });\n\n    if (!outlet && !hasTab) {\n      throw new Error('IonTabs must contain an IonRouterOutlet or an IonTab');\n    }\n    if (outlet && hasTab) {\n      throw new Error('IonTabs cannot contain an IonRouterOutlet and an IonTab at the same time');\n    }\n\n    if (hasTab) {\n      return <IonTabsInner {...this.props}></IonTabsInner>;\n    }\n\n    /**\n     * TODO(ROU-11051)\n     *\n     * There is no error handling for the case where there\n     * is no associated Route for the given IonTabButton.\n     *\n     * More investigation is needed to determine how to\n     * handle this to prevent any overwriting of the\n     * IonTabButton's onClick handler and how the routing","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/ionic-team/ionic-framework/blob/625f9c38ad8c5db8a6c12df5c1622a36da90f9e3/packages/react/src/components/navigation/IonTabs.tsx#L160-L196","documentation":"Thrown during the render of the React `<IonTabs>` component after it iterates children with `React.Children.forEach`. If no child resolves to an `IonRouterOutlet` (by type or `isRouterOutlet`) and no child resolves to an `IonTab`, the component refuses to render because it cannot determine the navigation model. Thrown during render, so React treats it as an uncaught error.","triggerScenarios":"Authoring `<IonTabs>` whose children are only `<IonTabBar>`, fragments without an outlet, plain elements, or `null` — i.e. neither an `<IonRouterOutlet>` nor an `<IonTab>` is among the direct children. Also triggered when children are passed as a function that returns no outlet/tab.","commonSituations":"Following an outdated tutorial that omitted the outlet; conditionally rendering the outlet such that it is absent on first render; tabs defined purely as a tab bar with routes declared elsewhere; refactor that moved the outlet into a child component whose type is not recognized.","solutions":["Add `<IonRouterOutlet>` (with `<Route>` children) as a direct child of `<IonTabs>` for router-driven tabs.","Or add at least one `<IonTab>` child for router-less tabs.","Make sure the outlet is a direct, statically-present child — avoid `condition && <IonRouterOutlet/>` that is false on first render.","If using a render-prop child, ensure the function returns a tree containing an `<IonRouterOutlet>` or `<IonTab>`."],"exampleFix":"// before\n<IonTabs>\n  <IonTabBar slot=\"bottom\">\n    <IonTabButton tab=\"home\"><IonLabel>Home</IonLabel></IonTabButton>\n  </IonTabBar>\n</IonTabs>\n// after\n<IonTabs>\n  <IonRouterOutlet>\n    <Route exact path=\"/home\" component={Home} />\n  </IonRouterOutlet>\n  <IonTabBar slot=\"bottom\">\n    <IonTabButton tab=\"home\" href=\"/home\"><IonLabel>Home</IonLabel></IonTabButton>\n  </IonTabBar>\n</IonTabs>","handlingStrategy":"validation","validationCode":"import { Fragment, isValidElement } from 'react';\nimport { IonRouterOutlet, IonTab } from '@ionic/react';\nfunction validateIonTabsChildren(children: React.ReactNode) {\n  let outlet = false, hasTab = false;\n  React.Children.forEach(children, (child: any) => {\n    if (child == null || typeof child !== 'object' || !('type' in child)) return;\n    if (child.type === IonRouterOutlet || child.type?.isRouterOutlet) outlet = true;\n    else if (child.type === Fragment && child.props?.children?.[0]?.type === IonRouterOutlet) outlet = true;\n    else if (child.type === IonTab) hasTab = true;\n  });\n  if (!outlet && !hasTab) throw new Error('IonTabs needs an IonRouterOutlet or IonTab child');\n}","typeGuard":"function hasTabsNavModel(children: React.ReactNode): boolean {\n  let outlet = false, hasTab = false;\n  React.Children.forEach(children, (child: any) => {\n    if (child?.type === IonRouterOutlet || child?.type?.isRouterOutlet) outlet = true;\n    else if (child?.type === IonTab) hasTab = true;\n  });\n  return outlet || hasTab;\n}","tryCatchPattern":null,"preventionTips":["Always render <IonRouterOutlet> or an <IonTab> as a direct child of <IonTabs>.","Avoid conditionally rendering the outlet on first render; use a guaranteed fallback.","When using a render-prop child, ensure it returns a tree containing the outlet or a tab."],"tags":["react","ion-tabs","navigation","render"],"backgroundTag":null,"analyzedSha":"625f9c38ad8c5db8a6c12df5c1622a36da90f9e3","analyzedAt":"2026-08-12T16:00:25.413Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}