Search code examples
reactjsreact-routerreact-router-domredux-toolkit

React user login is successful but can't redirect to admin page


Hello dear stackoverflow users. First of all, thank you in advance for your help. I have a react application in this application, I check the user login process when going to the /admin page, if the user is not logged in, I redirect to the /login page. I log in the user by making a request on the backend and create a token. If the user login is successful, I try to redirect to the /admin page, but I get an error. I haven't been able to solve it yet. I am sharing my codes with you.

router/index.jsx

  import { createBrowserRouter } from "react-router-dom";
import WebLayout from "~/../layouts/web";
import AdminLayout from "~/../layouts/admin";
import Home from "~/pages/web/home";
import Orders from "~/pages/admin/orders";
import Categories from "~/pages/admin/categories";
import Tables from "~/pages/admin/tables";
import Meals from "~/pages/admin/meals";
import AdminHome from "~/pages/admin/home";
import MealDetail from "~/pages/web/mealdetail";
import Login from "~/components/shared/login";
import PrivateRoute from "~/components/shared/privateroute";

const routes = createBrowserRouter([
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/",
    element: <WebLayout />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "meal/:mealId",
        element: <MealDetail />,
      },
    ],
  },
  {
    path: "/admin",
    element: <AdminLayout />,
    children: [
      {
        index: true,
        element: <PrivateRoute component={AdminHome} />,
      },
      {
        path: "orders",
        element: <PrivateRoute component={Orders} />,
      },
      {
        path: "categories",
        element: <PrivateRoute component={Categories} />,
      },
      {
        path: "tables",
        element: <PrivateRoute component={Tables} />,
      },
      {
        path: "meals",
        element: <PrivateRoute component={Meals} />,
      },
    ],
  },
]);

export default routes;

privateroute/index.jsx

import React from "react";
import { Route, Navigate } from "react-router-dom";
import { useSelector } from "react-redux";

const PrivateRoute = ({ element: Element, ...rest }) => {
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
  return isAuthenticated ? (
    <Route {...rest} element={<Element />} />
  ) : (
    <Navigate to="/login" />
  );
};

export default PrivateRoute;

login/index.jsx

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { loginAsync } from "~/store/authSlice";
import { useNavigate } from "react-router-dom"; // useNavigate hook'unu ekleyin

const Login = () => {
  const dispatch = useDispatch();
  const navigate = useNavigate(); // useNavigate hook'unu tanımlayın
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const handleLogin = async () => {
    try {
      // Redux Toolkit ile login işlemini gerçekleştiren action çağrısı
      await dispatch(loginAsync({ username, password }));
      // Login başarılı olduğunda /admin sayfasına yönlendir
      navigate("/admin"); // useNavigate hook'unu kullanarak yönlendirme işlemini gerçekleştirin
    } catch (error) {
      console.error("Login error:", error.message);
      // Handle login error, display a message to the user, etc.
    }
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};

export default Login;

authslice.js

   // src/store/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

const initialState = {
    token: null,
    username: null,
    isAuthenticated: false,
};


export const loginAsync = createAsyncThunk('auth/login', async ({ username, password }) => {
    try {

        const response = await axios.post('http://localhost:3000/api/login', {
            username,
            password,
        });
        console.log(response.data);

        const data = response.data;
        return { token: data.token, username };
    } catch (error) {

        throw new Error(error.response.data.error);
    }
});

const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {
        logout(state) {
            state.token = null;
            state.username = null;
            state.isAuthenticated = false;
        },
    },
    extraReducers: (builder) => {

        builder.addCase(loginAsync.fulfilled, (state, action) => {
            const { token, username } = action.payload;
            state.token = token;
            state.username = username;
            state.isAuthenticated = true;
        });
    },
});

export const { logout } = authSlice.actions;
export default authSlice.reducer;

and finally the error i got

Unexpected Application Error!
A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
Error: A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
    at invariant (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=9572ed87:202:11)
    at Route (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=9572ed87:3706:10)
    at renderWithHooks (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:12171:26)
    at mountIndeterminateComponent (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:14921:21)
    at beginWork (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:15902:22)
    at beginWork$1 (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19749:22)
    at performUnitOfWork (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19194:20)
    at workLoopSync (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19133:13)
    at renderRootSync (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19112:15)
    at recoverFromConcurrentError (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:18732:28)
💿 Hey developer 👋

You can provide a way better UX than this when your app throws errors by providing your own ErrorBoundary or errorElement prop on your route.

Solution

  • Change your route to:

            element: <PrivateRoute>YourComponents</PrivateRoute>,
    
    

    and private Route return statement from

      return isAuthenticated ? (
        <Route {...rest} element={<Element />} />
      ) : (
        <Navigate to="/login" />
      );
    

    to

      return isAuthenticated ? (
            <>
              {children}
              <Outlet />
            </>  ) : (
        <Navigate to="/login" />
      );
    

    Once you render the route, your private route element should render the children in the private route and the Outlet for other nested routes.