I am encountering an issue with React context while implementing user login functionality on my website. Users can log in as dummy users, and I've implemented this using React contexts. However, after a user logs in and navigates away from the page, the context doesn't persist, leading me to believe that the component is re-rendering.
GitHub: GitHub Repository
Additionally, you can experience the issue on the live website: Live Website
I would appreciate any assistance in understanding why the context is not persisting after navigating away from the page. Thank you for your help.
import { createContext, useState } from "react";
export const UserContext = createContext();
export const UserProvider = (props) => {
const [user, setUser] = useState("grumpy19");
return (
<UserContext.Provider value={{ user, setUser }}>
{props.children}
</UserContext.Provider>
);
};
import { useEffect, useState } from "react";
import { getUsers } from "../../utils/utils";
import { UserContext } from "../../contexts/UserContent";
import { useContext } from "react";
import "./Users.css";
import Spinner from "react-bootstrap/Spinner";
import { Card, CardGroup } from "react-bootstrap";
import { Badge } from "react-bootstrap";
import Stack from "react-bootstrap/Stack";
export default function Users() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [err, setErr] = useState(null);
const { setUser, user } = useContext(UserContext);
function handleAvatarClick(username) {
setUser(username);
}
useEffect(() => {
setIsLoading(true);
setErr(false);
getUsers()
.then(({ users }) => {
setUsers(users);
setIsLoading(false);
})
.catch((err) => {
setIsLoading(false);
setErr(err);
});
}, []);
if (err)
return (
<div>
<h1>Whoops</h1>
<h2>Status: {err.status}</h2>
<h2>{err.msg}</h2>
</div>
);
return (
<div className="user-container">
<h2 id="user-heading">Users</h2>
<p id="user-desc">Click on a users image to log in</p>
<p id="current-user">
Hello <Badge bg="dark">{user}</Badge> !
</p>
{isLoading ? (
<Spinner animation="border" variant="primary" className="spinner" />
) : null}
<CardGroup id="users-list">
<ul>
{users.map((person, index) => {
return (
<Card
key={index}
className="user-card"
onClick={() => {
handleAvatarClick(person.username);
}}
>
<Stack className="user-info" direction="horizontal" gap={3}>
<div className="p-2">{person.name}</div>
<div className="p-2 ms-auto">
{" "}
<Badge>{person.username}</Badge>
</div>
</Stack>
<Card.Img src={person.avatar_url} />
</Card>
);
})}
</ul>
</CardGroup>
</div>
);
}
I have implemented similar code in a previous project, and it worked without any problems.
import { createContext, useState } from "react";
export const UserContext = createContext();
export const UserProvider = (props) => {
const [user, setUser] = useState("Paul-R");
return (
<UserContext.Provider value={{ user, setUser }}>
{props.children}
</UserContext.Provider>
);
};
import { useEffect, useState } from "react";
import "./Users.css";
import { getUsers } from "../../utils.js/api";
import { UserContext } from "../../contexts/UserContent";
import { useContext } from "react";
export default function Users() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [err, setErr] = useState(null);
const { setUser } = useContext(UserContext);
function handleAvatarClick(username) {
setUser(username);
}
useEffect(() => {
setIsLoading(true);
setErr(false);
getUsers()
.then(({ users }) => {
setUsers(users);
setIsLoading(false);
})
.catch((err) => {
setIsLoading(false);
setErr(err);
});
}, []);
if (isLoading) return <p>Loading...</p>;
if (err)
return (
<div>
<h1>Whoops</h1>
<h2>Status: {err.status}</h2>
<h2>{err.msg}</h2>
</div>
);
return (
<div>
<h1>Users</h1>
<p>Click on a users image to log in</p>
<ul>
{users.map((user) => {
return (
<li key={user.username}>
<p>username: {user.username}</p>
<p>kudos: {user.kudos}</p>
<img
src={user.avatar_url}
alt=""
width="100"
onClick={() => {
handleAvatarClick(user.username);
}}
/>
</li>
);
})}
</ul>
</div>
);
}
The problem is that react-bootstrap's <Nav.Link>
component does not know you're using react-router and so doesn't integrate with it. When you click the link, it is doing a page refresh, which resets all states in the app.
You can use react-router's Link component as a standalone, like this:
import { Link } from 'react-router';
// ...
<Link to="/">Home</Link>
Or if you want to continue using react-bootstrap's Nav.Link, the as
prop can be used to tell it to render using a different component:
<Nav.Link as={Link} to="/">Home</Nav.Link>
Or, one more option, you could write an onClick handler and have it call the function from useNavigate
:
import { useNavigate } from 'react-router';
export default function Navigation() {
const navigate = useNavigate();
// ...
<Nav.Link onClick={() => navigate("/")}>Home</Nav.Link>
}