最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Next React Context not working as expected - Stack Overflow

programmeradmin2浏览0评论

I have implemented the following Context in my Next React web app in order to login to a remote REST API:

"use client";
import { createContext, useState, useEffect, useContext } from "react";
import { useRouter, usePathname } from "next/navigation";
import { useDialog } from "@/context/DialogContext";

const AuthContext = createContext({
    user: null,
    login: () => {},
    logout: () => {},
    loading: true,
});

export function AuthProvider({ children }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    const router = useRouter();
    const pathname = usePathname();
    const { showNotification } = useDialog();

    const checkSession = async () => {
        try {
            const res = await fetch("/api/login", {
                credentials: "include",
            });

            if (res.ok) {
                const data = await res.json();
                setUser(data);
            } else {
                setUser(null);
                showNotification("Sesión expirada", "error");
                router.push("/plataforma/login"); // Redirigir si la sesión expira
            }
        } catch (error) {
            console.error("Error validando sesión", error);
            setUser(null);
            router.push("/plataforma/login");
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => {
        checkSession(); // Validar en carga inicial
        const interval = setInterval(checkSession, 300000); // 5 minutos
        return () => clearInterval(interval);
    }, []);

    useEffect(() => {
        checkSession();
    }, [pathname]);

    const login = async (email, password) => {
        const res = await fetch("api/login", {
            method: "POST",
            credentials: "include",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({ email, password }),
        });

        const data = await res.json();
        if (res.ok) {
            setUser(data);
            router.push("/plataforma/years"); // Redirigir tras login
        } else {
            throw new Error(data.error);
        }
    };

    const logout = async () => {
        alert("Logout");
        try {
            const res = await fetch("/api/logout", {
                method: "POST",
                credentials: "include",
            });
            console.log('Logout response:', res);
            setUser(null);
            router.push("/plataforma/login");
        } catch (error) {
            console.error('Logout error:', error);
        }
    };

    return (
        <AuthContext.Provider value={{ user, login, logout, loading }}>
            {children}
        </AuthContext.Provider>
    );
}

export const useAuth = () => useContext(AuthContext);

And I use this context in my main layout.js as follows:

"use client";
import { useState, useEffect } from "react";
import { useAuth } from "@/context/AuthContext"; // Nuevo AuthContext para autenticación segura
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import { ThemeProvider } from "@mui/material/styles";
import theme from "@/theme";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { DialogProvider } from "@/context/DialogContext";
import { AuthProvider } from "@/context/AuthContext";
import HelpDrawer from "@/components/HelpDrawer";

import {
  Box,
  Button,
  Drawer,
  AppBar,
  CssBaseline,
  Toolbar,
  List,
  Typography,
  Divider,
  ListItem,
  ListItemButton,
  ListItemIcon,
  ListItemText,
  IconButton,
} from "@mui/material";

import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
import FlightTakeoffIcon from "@mui/icons-material/FlightTakeoff";
import HomeIcon from "@mui/icons-material/Home";
import MenuIcon from "@mui/icons-material/Menu";
import SettingsIcon from "@mui/icons-material/Settings";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import LogoutIcon from "@mui/icons-material/Logout";
import PersonIcon from "@mui/icons-material/Person";

const drawerWidth = 250;

export default function RootLayout({ children }) {
  const router = useRouter();
  const { user, loading, logout } = useAuth(); // Estado de autenticación
  console.log("Current user:", user); // Debugging statement
  const [mobileOpen, setMobileOpen] = useState(false);
  const [isClosing, setIsClosing] = useState(false);
  const [helpDrawerOpen, setHelpDrawerOpen] = useState(false);

  const handleDrawerToggle = () => {
    if (!isClosing) {
      setMobileOpen(!mobileOpen);
    }
  };

  const handleDrawerClose = () => {
    setIsClosing(true);
    setMobileOpen(false);
  };

  const handleDrawerTransitionEnd = () => {
    setIsClosing(false);
  };

  const handleHelpDrawerToggle = () => {
    setHelpDrawerOpen(!helpDrawerOpen);
  };

  const menuItems = [
    { text: "Inicio", icon: <HomeIcon />, path: "/plataforma" },
    { text: "Personal", icon: <PersonIcon />, path: user ? `/plataforma/users/edit/${user.id}` : "#" }, // Handle null user
    { text: "Años", icon: <CalendarMonthIcon />, path: "/plataforma/years" },
    { text: "Viajes", icon: <FlightTakeoffIcon />, path: "/plataforma/trips" },
  ];

  const bottomMenuItems = [
    { text: "Configuración", icon: <SettingsIcon />, path: "/plataforma/settings" },
    { text: "Ayuda", icon: <HelpOutlineIcon />, path: "/plataforma/help" },
  ];

  // Redirigir a login si no está autenticado
  useEffect(() => {
    if (!loading && !user) {
      router.push("/plataforma/login");
    } else {
      console.log("User inside useEffect:", user); // Debugging statement
    }
  }, [user, loading, router]);

  const drawerContent = (
    <Box sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <Toolbar />
      <Divider />
      
      {/* Mostrar nombre de usuario */}
      {user && (
        <Box sx={{ padding: 2, textAlign: "center" }}>
          <Typography variant="h6">Usuario: {user.name || "Nombre no disponible"}</Typography>
        </Box>
      )}
      
      <Divider />

      <List sx={{ color: theme.palette.secondary.main }}>
        {menuItems.map((item) => (
          <ListItem key={item.text} disablePadding>
            <ListItemButton onClick={() => router.push(item.path)}>
              <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{item.icon}</ListItemIcon>
              <ListItemText primary={item.text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>

      <Divider sx={{ mt: "auto" }} />

      <List sx={{ color: theme.palette.secondary.main }}>
        {bottomMenuItems.map((item) => (
          <ListItem key={item.text} disablePadding>
            <ListItemButton onClick={() => router.push(item.path)}>
              <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{item.icon}</ListItemIcon>
              <ListItemText primary={item.text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>
      <Divider />
      <List sx={{ color: theme.palette.secondary.main }}>
        <ListItem key="logout" disablePadding>
          <ListItemButton onClick={() => { logout(); }}>
            <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{<LogoutIcon />}</ListItemIcon>
            <ListItemText primary="Cerrar sesión" />
          </ListItemButton>
        </ListItem>
      </List>
    </Box>
  );

  return (
    <html lang="es">
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          <ThemeProvider theme={theme}>
            <DialogProvider>
              <AuthProvider>
                <Box sx={{ display: "flex" }}>
                  <CssBaseline />
                  <AppBar position="fixed" sx={{ backgroundColor: theme.palette.secondary.main, zIndex: (theme) => theme.zIndex.drawer + 1 }}>
                    <Toolbar>
                      <IconButton
                        color="inherit"
                        aria-label="open drawer"
                        edge="start"
                        onClick={handleDrawerToggle}
                        sx={{ mr: 2, display: { sm: "none" } }}
                      >
                        <MenuIcon />
                      </IconButton>
                      <Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", flexGrow: 1 }}>
                        <Typography variant="h6" sx={{ fontWeight: "bold" }}>
                          7p
                        </Typography>
                        <Image src="/i/easy50.png" alt="Logo" width={50} height={50} priority />
                        <Typography variant="h6" sx={{ fontWeight: "bold" }}>
                          facil.es
                        </Typography>
                      </Box>
                      <Box sx={{ marginLeft: "auto" }}>
                        <IconButton
                          color="inherit"
                          aria-label="open help drawer"
                          edge="end"
                          onClick={handleHelpDrawerToggle}
                        >
                          <HelpOutlineIcon />
                        </IconButton>
                      </Box>
                    </Toolbar>
                  </AppBar>
                  <Drawer
                    variant="temporary"
                    open={mobileOpen}
                    onTransitionEnd={handleDrawerTransitionEnd}
                    onClose={handleDrawerClose}
                    sx={{
                      display: { xs: "block", sm: "none" },
                      "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
                    }}
                  >
                    {drawerContent}
                  </Drawer>
                  <Drawer
                    variant="permanent"
                    sx={{
                      width: drawerWidth,
                      flexShrink: 0,
                      display: { xs: "none", sm: "block" },
                      "& .MuiDrawer-paper": { width: drawerWidth, boxSizing: "border-box" },
                    }}
                  >
                    {drawerContent}
                  </Drawer>
                  <Box component="main" sx={{ flexGrow: 1, p: 2, overflowX: "hidden", width: "100vw" }}>
                    <Toolbar />
                    {children}
                  </Box>
                  <HelpDrawer open={helpDrawerOpen} onClose={handleHelpDrawerToggle} />
                </Box>
              </AuthProvider>
            </DialogProvider>
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

The issue is that user from useAuth is always null, so I am not able to the the user name logegd in (user.name) or even call user.logout(). I checked the calls to the API in AuthContext and is working fine (user data is set). Any suggestions?

I have implemented the following Context in my Next React web app in order to login to a remote REST API:

"use client";
import { createContext, useState, useEffect, useContext } from "react";
import { useRouter, usePathname } from "next/navigation";
import { useDialog } from "@/context/DialogContext";

const AuthContext = createContext({
    user: null,
    login: () => {},
    logout: () => {},
    loading: true,
});

export function AuthProvider({ children }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    const router = useRouter();
    const pathname = usePathname();
    const { showNotification } = useDialog();

    const checkSession = async () => {
        try {
            const res = await fetch("/api/login", {
                credentials: "include",
            });

            if (res.ok) {
                const data = await res.json();
                setUser(data);
            } else {
                setUser(null);
                showNotification("Sesión expirada", "error");
                router.push("/plataforma/login"); // Redirigir si la sesión expira
            }
        } catch (error) {
            console.error("Error validando sesión", error);
            setUser(null);
            router.push("/plataforma/login");
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => {
        checkSession(); // Validar en carga inicial
        const interval = setInterval(checkSession, 300000); // 5 minutos
        return () => clearInterval(interval);
    }, []);

    useEffect(() => {
        checkSession();
    }, [pathname]);

    const login = async (email, password) => {
        const res = await fetch("api/login", {
            method: "POST",
            credentials: "include",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({ email, password }),
        });

        const data = await res.json();
        if (res.ok) {
            setUser(data);
            router.push("/plataforma/years"); // Redirigir tras login
        } else {
            throw new Error(data.error);
        }
    };

    const logout = async () => {
        alert("Logout");
        try {
            const res = await fetch("/api/logout", {
                method: "POST",
                credentials: "include",
            });
            console.log('Logout response:', res);
            setUser(null);
            router.push("/plataforma/login");
        } catch (error) {
            console.error('Logout error:', error);
        }
    };

    return (
        <AuthContext.Provider value={{ user, login, logout, loading }}>
            {children}
        </AuthContext.Provider>
    );
}

export const useAuth = () => useContext(AuthContext);

And I use this context in my main layout.js as follows:

"use client";
import { useState, useEffect } from "react";
import { useAuth } from "@/context/AuthContext"; // Nuevo AuthContext para autenticación segura
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import { ThemeProvider } from "@mui/material/styles";
import theme from "@/theme";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { DialogProvider } from "@/context/DialogContext";
import { AuthProvider } from "@/context/AuthContext";
import HelpDrawer from "@/components/HelpDrawer";

import {
  Box,
  Button,
  Drawer,
  AppBar,
  CssBaseline,
  Toolbar,
  List,
  Typography,
  Divider,
  ListItem,
  ListItemButton,
  ListItemIcon,
  ListItemText,
  IconButton,
} from "@mui/material";

import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
import FlightTakeoffIcon from "@mui/icons-material/FlightTakeoff";
import HomeIcon from "@mui/icons-material/Home";
import MenuIcon from "@mui/icons-material/Menu";
import SettingsIcon from "@mui/icons-material/Settings";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import LogoutIcon from "@mui/icons-material/Logout";
import PersonIcon from "@mui/icons-material/Person";

const drawerWidth = 250;

export default function RootLayout({ children }) {
  const router = useRouter();
  const { user, loading, logout } = useAuth(); // Estado de autenticación
  console.log("Current user:", user); // Debugging statement
  const [mobileOpen, setMobileOpen] = useState(false);
  const [isClosing, setIsClosing] = useState(false);
  const [helpDrawerOpen, setHelpDrawerOpen] = useState(false);

  const handleDrawerToggle = () => {
    if (!isClosing) {
      setMobileOpen(!mobileOpen);
    }
  };

  const handleDrawerClose = () => {
    setIsClosing(true);
    setMobileOpen(false);
  };

  const handleDrawerTransitionEnd = () => {
    setIsClosing(false);
  };

  const handleHelpDrawerToggle = () => {
    setHelpDrawerOpen(!helpDrawerOpen);
  };

  const menuItems = [
    { text: "Inicio", icon: <HomeIcon />, path: "/plataforma" },
    { text: "Personal", icon: <PersonIcon />, path: user ? `/plataforma/users/edit/${user.id}` : "#" }, // Handle null user
    { text: "Años", icon: <CalendarMonthIcon />, path: "/plataforma/years" },
    { text: "Viajes", icon: <FlightTakeoffIcon />, path: "/plataforma/trips" },
  ];

  const bottomMenuItems = [
    { text: "Configuración", icon: <SettingsIcon />, path: "/plataforma/settings" },
    { text: "Ayuda", icon: <HelpOutlineIcon />, path: "/plataforma/help" },
  ];

  // Redirigir a login si no está autenticado
  useEffect(() => {
    if (!loading && !user) {
      router.push("/plataforma/login");
    } else {
      console.log("User inside useEffect:", user); // Debugging statement
    }
  }, [user, loading, router]);

  const drawerContent = (
    <Box sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <Toolbar />
      <Divider />
      
      {/* Mostrar nombre de usuario */}
      {user && (
        <Box sx={{ padding: 2, textAlign: "center" }}>
          <Typography variant="h6">Usuario: {user.name || "Nombre no disponible"}</Typography>
        </Box>
      )}
      
      <Divider />

      <List sx={{ color: theme.palette.secondary.main }}>
        {menuItems.map((item) => (
          <ListItem key={item.text} disablePadding>
            <ListItemButton onClick={() => router.push(item.path)}>
              <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{item.icon}</ListItemIcon>
              <ListItemText primary={item.text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>

      <Divider sx={{ mt: "auto" }} />

      <List sx={{ color: theme.palette.secondary.main }}>
        {bottomMenuItems.map((item) => (
          <ListItem key={item.text} disablePadding>
            <ListItemButton onClick={() => router.push(item.path)}>
              <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{item.icon}</ListItemIcon>
              <ListItemText primary={item.text} />
            </ListItemButton>
          </ListItem>
        ))}
      </List>
      <Divider />
      <List sx={{ color: theme.palette.secondary.main }}>
        <ListItem key="logout" disablePadding>
          <ListItemButton onClick={() => { logout(); }}>
            <ListItemIcon sx={{ color: theme.palette.secondary.main }}>{<LogoutIcon />}</ListItemIcon>
            <ListItemText primary="Cerrar sesión" />
          </ListItemButton>
        </ListItem>
      </List>
    </Box>
  );

  return (
    <html lang="es">
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          <ThemeProvider theme={theme}>
            <DialogProvider>
              <AuthProvider>
                <Box sx={{ display: "flex" }}>
                  <CssBaseline />
                  <AppBar position="fixed" sx={{ backgroundColor: theme.palette.secondary.main, zIndex: (theme) => theme.zIndex.drawer + 1 }}>
                    <Toolbar>
                      <IconButton
                        color="inherit"
                        aria-label="open drawer"
                        edge="start"
                        onClick={handleDrawerToggle}
                        sx={{ mr: 2, display: { sm: "none" } }}
                      >
                        <MenuIcon />
                      </IconButton>
                      <Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", flexGrow: 1 }}>
                        <Typography variant="h6" sx={{ fontWeight: "bold" }}>
                          7p
                        </Typography>
                        <Image src="/i/easy50.png" alt="Logo" width={50} height={50} priority />
                        <Typography variant="h6" sx={{ fontWeight: "bold" }}>
                          facil.es
                        </Typography>
                      </Box>
                      <Box sx={{ marginLeft: "auto" }}>
                        <IconButton
                          color="inherit"
                          aria-label="open help drawer"
                          edge="end"
                          onClick={handleHelpDrawerToggle}
                        >
                          <HelpOutlineIcon />
                        </IconButton>
                      </Box>
                    </Toolbar>
                  </AppBar>
                  <Drawer
                    variant="temporary"
                    open={mobileOpen}
                    onTransitionEnd={handleDrawerTransitionEnd}
                    onClose={handleDrawerClose}
                    sx={{
                      display: { xs: "block", sm: "none" },
                      "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
                    }}
                  >
                    {drawerContent}
                  </Drawer>
                  <Drawer
                    variant="permanent"
                    sx={{
                      width: drawerWidth,
                      flexShrink: 0,
                      display: { xs: "none", sm: "block" },
                      "& .MuiDrawer-paper": { width: drawerWidth, boxSizing: "border-box" },
                    }}
                  >
                    {drawerContent}
                  </Drawer>
                  <Box component="main" sx={{ flexGrow: 1, p: 2, overflowX: "hidden", width: "100vw" }}>
                    <Toolbar />
                    {children}
                  </Box>
                  <HelpDrawer open={helpDrawerOpen} onClose={handleHelpDrawerToggle} />
                </Box>
              </AuthProvider>
            </DialogProvider>
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

The issue is that user from useAuth is always null, so I am not able to the the user name logegd in (user.name) or even call user.logout(). I checked the calls to the API in AuthContext and is working fine (user data is set). Any suggestions?

Share Improve this question asked Mar 14 at 16:08 soyxansoyxan 1031 silver badge12 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

The useAuth can only be called by components that are inside your AuthProvider.

In your case I'd do something like this:

layout.js

return (
    <html lang="es">
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          <ThemeProvider theme={theme}>
            <DialogProvider>
              <AuthProvider>
                <BaseLayout>
                  {children}
                </BaseLayout>
              </AuthProvider>
            </DialogProvider>
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
)

And then inside your new Baselayout component you'd have the rest of your logic and user management. Your drawers and headers. Inside the Baselayout and the {children} (which would be your pages) you can use your hook.

发布评论

评论列表(0)

  1. 暂无评论