8

I created an ionic react app with tabs starter template from the cli and added login functionality to the existing structure.

What I did :

App.tsx

  const App: React.FC = () => (
  <IonApp>
    <IonReactRouter>
      <IonTabs>
        <IonRouterOutlet>
          <Route path="/profile" component={Profile} exact={true} />
          <Route path="/history" component={History} />
          <Route path="/login" component={Login} exact={true} />
          <Route path="/" component={Login} exact={true} />
        </IonRouterOutlet>
        <IonTabBar slot="bottom">
          <IonTabButton tab="profile" href="/profile">
            <IonIcon icon={personCircleOutline} />
            <IonLabel>Profile</IonLabel>
          </IonTabButton>
          <IonTabButton tab="history" href="/history">
            <IonIcon icon={documentTextOutline} />
            <IonLabel>History</IonLabel>
          </IonTabButton>
        </IonTabBar>
      </IonTabs>
    </IonReactRouter>
  </IonApp>
);

The issue is that I'm seeing all the tabs on the login screen. I want the tabs to show only after the user logs in.

Working Demo here

kkakroo
  • 1,043
  • 2
  • 11
  • 21

2 Answers2

12

You need to separate your private tabs into another component:

mainTabs.tsx

const MainTabs: React.FC = () => {
  return (
    <IonTabs>
      <IonRouterOutlet>
        <Redirect exact path="/" to="/profile" />
        <Route path="/profile" render={() => <Profile />} exact={true} />
        <Route path="history" render={() => <History />} exact={true} />
      </IonRouterOutlet>
      <IonTabBar slot="bottom">
        <IonTabButton tab="profile" href="/profile">
          <IonIcon icon={personCircleOutline} />
          <IonLabel>Profile</IonLabel>
        </IonTabButton>
        <IonTabButton tab="history" href="/history">
          <IonIcon icon={documentTextOutline} />
          <IonLabel>History</IonLabel>
        </IonTabButton>
      </IonTabBar>
    </IonTabs>
  );
};

export default MainTabs;

After that, you can create a route inside App.tsx in which you conditionally render the private Tabs if the user is logged in. Otherwise, the Login page is rendered:

App.tsx

return (
  <IonApp>
    <IonReactRouter>
      <Route path="/login" component={Login} exact={true} />
      <Route path="/" component={isLoggedIn ? MainTabs : Login} />
    </IonReactRouter>
  </IonApp>
);

At this point, you want to have some sort of state management setup (Redux, custom state management through React hooks, etc.) because you need to have your isLoggedIn state inside the App component but modify it from the Login component (this get's messy real quick using props only).

So as a quick example (you can definitely do a better job), I've created a simple user context, in which I inject the function that updates the isLoggedIn state:

App.tsx

interface IUserManager {
  setIsLoggedIn: Function;
}

const user: IUserManager = {
  setIsLoggedIn: () => {}
};

export const UserContext = React.createContext<IUserManager>(user);

const IonicApp: React.FC = () => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const user = useContext(UserContext);

  user.setIsLoggedIn = setIsLoggedIn;

  return (
    <IonApp>
      <IonReactRouter>
        <Route path="/login" component={Login} exact={true} />
        <Route path="/" component={isLoggedIn ? MainTabs : Login} />
      </IonReactRouter>
    </IonApp>
  );
};

const App: React.FC = () => {
  return (
    <UserContext.Provider value={user}>
      <IonicApp />
    </UserContext.Provider>
  );
};

I also need to create a top-level App component so I can provide the context and use it right away inside the IonApp component. After that, I can simply use the user context inside the Login component and update the state on login:

const user = useContext(UserContext);

const loginClick = () => {
  if (userName === "a" && password === "a") {
    user.setIsLoggedIn(true);
  } 
};

Here is your forked demo.

htmn
  • 1,505
  • 2
  • 15
  • 26
0

Do not wrap routes in <IonTabs /> when on login screen.

One caveat is that Ionic expects a particular nesting of route related components (at least in my experience). Specifically, there can only be one <IonRouterOutlet /> (when you do the application becomes unresponsive and no longer receives clicks). One way to tackle this is to render the same routes wrapped in different components depending on whether the user is logged in. In the example below I render a tree without <IonTabs /> when the user is not logged in. Note I declared the same routes in either case (doing otherwise resulted in weird redirect loops).

// Determine if user is logged in, e.g.
const auth = useAuth()
const showTabs = !!auth.user

// Always declare same routes
const routes = (
  <>
    <Route path="/login" exact />
    <ProtectedRoute path="/protected" exact />
  </>
)

// Adhere to Ionic's nesting requirements
// Tabs: IonReactRouter -> IonTabs -> IonRouterOutlet -> Route
// No tabs: IonReactRouter -> IonRouterOutlet -> Route
return (
  <IonReactRouter>
    {showTabs ? (
      <IonTabs>
        <IonRouterOutlet>{routes}</IonRouterOutlet>
        {tabBar}
      </IonTabs>
    ) : (
      <IonRouterOutlet>{routes}</IonRouterOutlet>
    )}
  </IonReactRouter>
)
trkoch
  • 2,688
  • 1
  • 16
  • 17