useContext returns null value in some components - reactjs

I have a react component index.js that fetches the user information from a Laravel server through a fetch call. The information is provided to the context UserContext, which is declared in a separate file and imported, near the top-level of the app as follows.
UserContext.jsx
import { createContext } from "react";
const UserContext = createContext(null);
export default UserContext;
const [user, setUser] = useState(null);
useEffect(() => {
// Get logged in user
fetch('/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}).then(res => res.json()).then(data => {
setUser(data);
}).catch(err => {
setUser(null)
});
}
return (
<UserContext.Provider value={user}>
<Header/>
<main>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home categories={categories} />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/profile/:id" element={<Profile />} />
<Route path="/profile/:id/edit" element={<EditProfile />} />
<Route path="/profile/:id/registrations" element={<MyRegistrations />} />
<Route path="/posts" element={<Posts />}></Route>
<Route path="/posts/:id" element={<Post categories={categories} />}></Route>
<Route path="/category/:id" element={<Posts />} />
<Route path="/403" element={<Error error={403} />} />
<Route path="/404" element={<Error error={404} />} />
<Route path="/500" element={<Error error={500} />} />
<Route path="*" element={<Error error={404} />} />
</Routes>
</BrowserRouter>
</main>
<Footer/>
</UserContext.Provider>
);
When I try to access user in <Header>, it works as expected. However, in the routes nested in the react-router-dom <Routes>, it returns null.
Header.jsx, Post.jsx declaration to access user
const user = useContext(UserContext);
I suspected that this could be caused because of the context provided by react-router-dom or the nesting, so I tried to pull <Post/> to the top level and disable react-router-dom but it seems to be the same issue.
Post.jsx
import { useState, useEffect, useContext } from 'react';
import React from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import * as DOMPurify from 'dompurify';
import { Container } from 'react-bootstrap';
import UserContext from '../context/userContext';
export default function Post({ categories }) {
const user = React.useContext(UserContext);
let navigate = useNavigate();
let params = useParams();
const [id, setId] = useState(params.id);
const [post, setPost] = useState(null);
const [registered, setRegistered] = useState(false);
const inCourse = (id, courses) => {
console.log(id, courses);
return courses.filter(course => course.id == id).length > 0;
}
const inEvent = (id, events) => {
return events.filter(event => event.id == id).length > 0;
}
useEffect(() => {
{/* fetch post from api, works correctly */}
}, [id]);
useEffect(() => {
}, [user, id]);
return (
{/* unnecessary jsx */}
);
}

Related

(react) only url change but page is not reloaded

import { useEffect, useState } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Detail from "../components/Detail";
import Popular from "../components/Popular";
import Home from "../components/Home ";
import Trending from "../components/Trending";
import Upcoming from "../components/Upcoming";
import TopRated from "../components/TopRated";
import NowPlaying from "../components/NowPlaying";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/popular-mv" element={<Popular type="movie" />} />
<Route path="/trending-mv" element={<Trending type="movie" />} />
<Route path="/topRated-mv" element={<TopRated type="movie" />} />
<Route path="/upcoming-mv" element={<Upcoming type="movie" />} />
<Route path="/nowPlaying-mv" element={<NowPlaying type="movie" />} />
<Route path="/popular-tv" element={<Popular type="tv" />} />
<Route path="/trending-tv" element={<Trending type="tv" />} />
<Route path="/topRated-tv" element={<TopRated type="tv" />} />
<Route path="/nowPlaying-tv" element={<NowPlaying type="tv" />} />
<Route path="/detail/:id/:type" element={<Detail />} />
</Routes>
</Router>
);
}
export default App;
import { useState, useEffect } from "react";
import { useParams, Link, useLocation } from "react-router-dom";
function Detail() {
const type =
useLocation().pathname.split("/")[
useLocation().pathname.split("/").length - 1
];
const URL = `https://api.themoviedb.org/3/${type}/`;
const { id } = useParams();
const [details, setDetails] = useState([]);
const [similars, setSimilars] = useState([]);
const [genres, setGenres] = useState([]);
function detailFetch() {
fetch(`${URL}${id}?api_key=${API_KEY}`)
.then((result) => result.json())
.then((json) => {
setDetails(json);
setGenres(json.genres);
});
}
function similarFetch() {
fetch(`${URL}${id}/similar?api_key=${API_KEY}`)
.then((result) => result.json())
.then((json) => {
setSimilars(json.results);
});
}
useEffect(() => {
detailFetch();
similarFetch();
}, []);
return (
<div>
<img src={`https://image.tmdb.org/t/p/w500/${details.poster_path}`} />
<div>{details.title}</div>
<div>{details.vote_average}</div>
<div>{details.release_date}</div>
<div>{details.runtime}</div>
<div>
{genres.map((g) => (
<li key={g.id}>{g.name}</li>
))}
</div>
<p>{details.overview}</p>
<div>
{similars.map((s) => (
<a href={`/detail/${s.id}/${type}`} key={s.id}>
<img src={`https://image.tmdb.org/t/p/w500/${s.poster_path}`} />
</a>
))}
</div>
</div>
);
}
export default Detail;
I'm making movieApp using react.
but i have some problems about routing.
As i said at title, in movie information(detail) page, i made this page load when clicking movie poster and its similar movies. and then i cliked similar movie poster, i expected to reload the page.
Actially i solve this problem using not using .
But i have question why this is not working when i using
(i have other pages like tv list,movie list. but it only occurs in detail page.)
Thank You!

private route in react

I'm using React Router v6 and am creating private routes for my application.
i must to close all routes except login until user is authenticated
this is my private route
import React from 'react';
import { Navigate } from 'react-router-dom';
import loginAction from '#/api/AuthProvider';
export function PrivateRoute({ children }) {
const { user } = loginAction();
return user ? children : <Navigate to="/login" />;
}
this is my request
export default function loginAction() {
return new Promise((resolve) => {
resolve({
user: {
fullName: 'Elon Mask',
dob: '2022-07-27T12:46:26.356Z',
email: 'user#gmail.com',
defaultCurrency: 'USD'
},
token: 'DTYHKL57HGGJ'
});
}).then((data) => {
localStorage.setItem('token', data.token);
return data;
});
}
This is my App
export const App = () => {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
element={
<PrivateRoute>
<AppLayout />
</PrivateRoute>
}
>
<Route
path="/"
element={
<PrivateRoute>
<Landing />
</PrivateRoute>
}
/>
<Route
path="/about"
element={
<PrivateRoute>
<About />
</PrivateRoute>
}
/>
<Route
path="/categories"
element={
<PrivateRoute>
<Categories />
</PrivateRoute>
}
/>
<Route
path="*"
element={
<PrivateRoute>
<Fallback />
</PrivateRoute>
}
/>
<Route
path="/expenses"
element={
<PrivateRoute>
<Expenses />
</PrivateRoute>
}
/>
</Route>
</Routes>
</BrowserRouter>
</QueryClientProvider>
);
};
but when i submit, it does not redirect me to the home page
what i am doing wrong? Is there something I'm missing?
You're trying to use loginAction as a react hook when in reality it's an asynchronous function that returns a promise.
The way that I would suggest solving this is converting the loginAction action into a react hook. Read more about hooks here: https://reactjs.org/docs/hooks-intro.html
Essentially the two main parts of a hook are that:
The name starts with the word use such as useLoginAction
It can create/return state variables and when those state variables changes, components that use the hook will also re-render.
So the first change is wrapping loginAction into a hook:
If you don't understand how the useEffect hook works, look at this: https://reactjs.org/docs/hooks-effect.html
import { useState, useEffect } from "react";
function loginAction() {
return new Promise((resolve) => {
resolve({
user: {
fullName: 'Elon Mask',
dob: '2022-07-27T12:46:26.356Z',
email: 'user#gmail.com',
defaultCurrency: 'USD'
},
token: 'DTYHKL57HGGJ'
});
}).then((data) => {
localStorage.setItem('token', data.token);
return data;
});
}
export default function useLoginAction(){
const [data, setData] = useState({ user: null });
useEffect(() => {
loginAction().then((response) => setData(response));
}, []);
return data;
}
After that you can use the hook in your private route function. There are less repetitive ways to conditionally render the routes but that's outside the scope of this question:
import React from 'react';
import { Navigate } from 'react-router-dom';
import useLoginAction from '#/api/AuthProvider';
export function PrivateRoute({ children }) {
const { user } = useLoginAction();
return user ? children : <Navigate to="/login" />;
}
And this should work the way you're expecting it to work from my understanding.
You can control access to routes by some flag (isAuth) or other value. I'm doing this:
const App = () => {
const navigate = useNavigate()
const accessToken = useAppSelector((state) => state.authSlice.accessToken)
useEffect(() => {
if (accessToken === '') {
navigate('/login', { replace: true })
} else {
navigate('/', { replace: true })
}
}, [accessToken])
return (
<>
{accessToken !== '' ? (
<Routes>
<Route path='/' element={<Main />} />
<Route path='/profile' element={<Profile />} />
<Route path='*' element={<NotFound />} />
</Routes>
) : (
<Routes>
<Route path='/login' element={<Login />} />
<Route path='/registration' element={<Registration />} />
<Route path='*' element={<NotFound />} />
</Routes>
)}
<StatusBar />
</>
)
}
export default App

undefiined useContext destructure

i am trying save user object in context but i am gettin g undefined
this is my context:
import { createContext } from "react";
export const UserContext = createContext(null)
this is routs :
import { UserContext } from './contexts/UserContext.js';
const [user, setUser] = useState();
<UserContext.Provider value={{user:user , setUser:setUser}}>
<Routes>
<Route path="/login" exact element={ <Login />} />
<Route path="/numberinput" exact element={<NumberInput />} />
<Route path="/VerifyPhone" exact element={<VerifyPhone />} />
<Route path="/Register" exact element={<Register />} />
<Route path="/ChangePassword" exact element={<ChangePassword />} />
<Route path="/" exact element={<PrivateRoute><Home /></PrivateRoute>} />
{/* <Route path="/Answers" exact element={<Answers />} />
<Route path="/results/:id/:quizzes_id" exact element={<Results />} /> */}
<Route path="/payment" element={<PrivateRoute><Payment /></PrivateRoute>} />
<Route path="/*" element={<FourOFour />} />
</Routes>
</UserContext.Provider>
and this is how i want to store data in another file:
import { UserContext } from '../../contexts/UserContext.js';
const { setUser } = useContext(UserContext);
baseUrl
.post('api/v1/login', data)
.then((response) => {
setUser(response.data.data);
console.log(response.data.data);
Swal.fire({
icon: 'success',
title: response.data.data.message,
showConfirmButton: false,
timer: 1000,
}).then(() => {
window.location.pathname = '/';
});
})
and when i log the user in '/' rout i am getting undefiend
You should initialize properties of the context in first parameter of the createContext function as follows,
const userContext = createContext({ user: null, setUser: () => {} })
You forgot to add an initial value to the useState hook.
const [user, setUser] = useState(null);
And,
Don't use only a console.log() to log the user as it runs only once when your App mounts.
Do this instead to log the user every time it changes:
// state
const [user, setUser] = useState(null);
// log user every time it changes
useEffect(()=> {
console.log(user, "user from effect hook")
}, [user])
Set user in Login component
import React from "react";
// import UserContext correctly
import { UserContext } from "<path-to-UserContext>";
export default function Login() {
const { user, setUser } = useContext(UserContext);
// set the user
useEffect(() => {
setUser("something");
}, []);
return <></>;
}
Note: I'm assuming that you are getting the data from API correctly.

Conditional Route not rendering the redirected Layout

In AppRouter, I have a conditional route with redirect for <AdminLayout/>.
relevant snippet:
<Route
exact
path="/admin"
strict
render={(props) => <AdminLayout {...props} />}
>
{loggedIn ? <Redirect to="/admin/summary" /> : <Login />}
</Route>
If loggedIn is true then, redirect to /admin/summary else redirect it back to <Login/>
The problem is: it is only changing the URL but not rendering the <AdminLayout/>.
Not sure where I am going wrong and what I am missing.
UPDATED PrivateRoute and AppRouter below
AppRouter
import React, { useEffect } from "react";
import { Router, Route, Switch, Redirect } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { createBrowserHistory } from "history";
import { alertActions } from "../actions";
import { history } from "../helpers";
import AdminLayout from "layouts/Admin/Admin.js";
import AuthLayout from "layouts/Auth/Auth.js";
import ResetPassword from "../components/pages/reset-password/ResetPassword";
import MailReset from "../components/pages/reset-password/MailReset";
import PrivateRoute from "../routes/PrivateRoute";
import Dashboard from "views/Dashboard";
const hist = createBrowserHistory();
const AppRouter = () => {
const alert = useSelector((state) => state.alert);
const dispatch = useDispatch();
useEffect(() => {
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}, []);
return (
<Router history={hist}>
<Switch>
{/* <Route path="/admin" render={(props) => <AdminLayout {...props} />} /> */}
<PrivateRoute exact path="/admin">
<Dashboard />
</PrivateRoute>
<Route
path="/auth/login"
render={(props) => <AuthLayout {...props} />}
/>
<Route exact path="/auth/forgotPassword" component={ResetPassword} />
<Route exact path="/auth/mail_reset" component={MailReset} />
<Redirect from="*" to="/auth/login" />
</Switch>
</Router>
);
};
export default AppRouter;
PrivateRoute
import React from "react";
import { Route, Redirect } from "react-router-dom";
import AdminLayout from "../layouts/Admin/Admin";
function PrivateRoute({ component: Component, roles, ...rest }) {
console.log("rest pvt route", ...rest);
return (
<Route
{...rest}
render={(props) => {
console.log("propsssss", props);
// if (!localStorage.getItem('userid')) {
if (!localStorage.getItem("access_token")) {
// not logged in so redirect to login page with the return url
return (
<Redirect
to={{ pathname: "/auth/login", state: { from: props.location } }}
/>
);
}
// logged in so return component
return <AdminLayout {...props} />;
}}
/>
);
}
export default { PrivateRoute };
So trying to explain what its is wrong:
You are setting rendering child and render props that's why children props takes priority here:
<Route
exact
path="/admin"
render={(props) => <AdminLayout {...props} />}
>
{loggedIn ? <Redirect to="/admin/summary" /> : <Login />}
</Route>
Your private route is correct but need to add your layout as well:
return <AdminLayout {...props} /><Component {...props} /></AdminLayout/>;
Inside app route you need to import PrivateRoute component it will look like this:
import PrivateRoute from './PrivateRoute';
const AppRouter = () => {
const alert = useSelector((state) => state.alert);
const loggedIn = useSelector((state) => state.authentication.loggedIn);
const dispatch = useDispatch();
useEffect(() => {
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}, []);
return (
<Router history={hist}>
<Switch>
<PrivateRoute exact path='/admin'>
<YOUR AUTH COMPONENT WHICH YOU WANT TO RENDER />
</PrivateRoute>
<Route
path='/auth/login'
render={(props) => <AuthLayout {...props} />}
/>
<Route exact path='/auth/forgotPassword' component={ResetPassword} />
<Route exact path='/auth/mail_reset' component={MailReset} />
<Redirect from='*' to='/auth/login' />
</Switch>
</Router>
);
};
Here I created demo code of this. Take reference from it: https://codesandbox.io/s/react-router-redirects-auth-forked-6q6o4?file=/example.js

Lazy loaded React router routes loading anyway

I've been trying to lazy load routes in React using React.lazy and Suspense. But some components are loading regardless of the current route, exactly: Feed, Profile and Settings.
Notice I don't actually want to lazy load Components like MenuAppBar and SnackAlert but if I import them normally and remove their Suspense, code-splitting straight doesn't even work and everything loads and the whole app is just a single chunk.
import {createMuiTheme, MuiThemeProvider} from "#material-ui/core";
import {yellow} from "#material-ui/core/colors";
import CssBaseline from "#material-ui/core/CssBaseline";
import axios from "axios";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import "./css/feed.css";
import "./css/style.css";
const Feed = lazy(() => import("./routes/Feed"));
const Profile = lazy(() => import("./routes/Profile"));
const Home = lazy(() => import("./routes/Home"));
const Settings = lazy(() => import("./routes/Settings"));
const NotFound = lazy(() => import("./routes/NotFound"));
const MenuAppBar = lazy(() => import("./components/MenuAppBar"));
const SnackAlert = lazy(() => import("./components/SnackAlert"));
const App: React.FC = () => {
const [isLogged, setIsLogged] = useState(localStorage.getItem("token") ? true : false);
const [user, setUser] = useState<User>(
isLogged ? JSON.parse(localStorage.getItem("userInfo") as string) : {admin: false}
);
const [openError, setOpenError] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<string>("");
const [severity, setSeverity] = useState<Severity>(undefined);
const [pwa, setPwa] = useState<any>(null);
const [showBtn, setShowBtn] = useState<boolean>(false);
const [isLight, setIsLight] = useState<boolean>(
(JSON.parse(localStorage.getItem("theme") as string) as boolean) ? true : false
);
const theme: customTheme = {
darkTheme: {
palette: {
type: "dark",
primary: {
main: yellow[600]
}
}
},
lightTheme: {
palette: {
type: "light",
primary: {
main: yellow[700]
}
}
}
};
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault();
setPwa(event);
setShowBtn(true);
});
window.addEventListener("appinstalled", (e) => {
setShowBtn(false);
setErrorMsg("App installed!");
setSeverity("success");
setOpenError(true);
});
const handleClick = () => {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
setErrorMsg(`Please, open the share menu and select "Add to Home Screen"`);
setSeverity("info");
setOpenError(true);
} else {
if (pwa) {
pwa.prompt();
pwa.userChoice.then((choiceResult: {outcome: "accepted" | "refused"}) => {
if (choiceResult.outcome === "accepted") {
setErrorMsg("App downloading in the background..");
setSeverity("info");
setOpenError(true);
}
setPwa(null);
});
}
}
};
useEffect(() => {
const token: string | null = localStorage.getItem("token");
let userInfo: User = JSON.parse(localStorage.getItem("userInfo") as string);
if (userInfo && token && !userInfo.admin) {
setUser(userInfo);
setIsLogged(true);
}
if (isLogged) {
axios
.get("/api/auth/user", {
headers: {
"x-auth-token": `${token}`
}
})
.then((res) => {
if (!userInfo || !token) {
setUser(res.data as User);
}
localStorage.setItem(`userInfo`, JSON.stringify(res.data as User));
setIsLogged(true);
})
.catch((err) => {
if (err) {
setIsLogged(false);
}
});
} else {
localStorage.removeItem("token");
localStorage.removeItem("userInfo");
}
}, [isLogged]);
return (
<MuiThemeProvider theme={isLight ? createMuiTheme(theme.lightTheme) : createMuiTheme(theme.darkTheme)}>
<CssBaseline />
<Router>
<Suspense fallback={<div></div>}>
<Route
path="/"
render={() => (
<>
<MenuAppBar
isLogged={isLogged}
setIsLogged={setIsLogged}
user={user}
setUser={setUser}
isLight={isLight}
setIsLight={setIsLight}
/>
<SnackAlert severity={severity} errorMsg={errorMsg} setOpenError={setOpenError} openError={openError} />
</>
)}
/>
</Suspense>
<Suspense fallback={<div></div>}>
<Switch>
<Route exact path="/" render={() => <Home />} />
<Route exact path="/profile/:id" render={() => <Profile />} />
<Route exact path="/feed" render={() => <Feed isLogged={isLogged} user={user} />} />
<Route
exact
path="/settings"
render={() => (
<Settings isLight={isLight} setIsLight={setIsLight} handleClick={handleClick} showBtn={showBtn} />
)}
/>
<Route render={() => <NotFound />} />
</Switch>
</Suspense>
</Router>
</MuiThemeProvider>
);
};
export default App;
You are wrapping your entire Switch in a single Suspense, so all components will be lazily loaded at the same time. You probably only want each to be fetched/loaded when the specific route is rendered the first time.
<Switch>
<Route
exact
path="/"
render={props => (
<Suspense fallback={<div>Loading...<div>}>
<Home {...props} />
</Suspense>
)}
/>
<Route
exact
path="/profile/:id"
render={props => (
<Suspense fallback={<div>Loading...<div>}>
<Profile {...props} />
</Suspense>
)}
/>
<Route
exact
path="/feed"
render={() => (
<Suspense fallback={<div>Loading...<div>}>
<Feed isLogged={isLogged} user={user} {...props} />
</Suspense>
)}
/>
<Route
exact
path="/settings"
render={() => (
<Suspense fallback={<div>Loading...<div>}>
<Settings
isLight={isLight}
setIsLight={setIsLight}
handleClick={handleClick}
showBtn={showBtn}
{...props}
/>
</Suspense>
)}
/>
<Route
render={() => <NotFound />}
/>
</Switch>
There is a lot of repetition here, so it is practical to factor out the suspense into a HOC.
const withSuspense = (WrappedComponent, fallback) => props => (
<Suspense fallback={fallback}>
<WrappedComponent {...props} />
</Suspense>
);
You can either decorate each perspective default export, i.e.
export default withSuspense(Home, <div>Loading...<div>);
App.js
...
<Switch>
<Route exact path="/" render={props => <Home {...props} />} />
or decorate them in your App
const HomeWithSuspense = withSuspense(Home, <div>Loading...<div>);
...
<Switch>
<Route
exact
path="/"
render={props => <HomeWithSuspense {...props} />}
/>
...
</Switch>
In case someone is having the same problem, the actual problem was that some of the components had other components within them which weren't exported as default and that's why they weren't being lazy-loaded.
So if you're having the same problem, you should check the import tree of the component you're trying to lazy-load and make sure every component in this tree is exported as default.
For more information refer to the named exports section in the react docs.
Thanks everyone for your help!
That should work, I would look other problems, like build scripts, or some other piece of code using those same bundles. (e.g. the inheritance thing you mentioned in comments)
Please try this once if above are worked
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./components/Home"));
const About = lazy(() => import("./components/About"));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route exact path='/' element={<Home/>}/>
<Route exact path='/about' element={<About/>}/>
</Routes>
</Suspense>
</Router>
);
export default App
There's no need for the whole tree that you're trying to lazy load to have default imports and exports. The component tree with its unique dependencies will be bundled into lazy chunk by default.
For eg.
Component.js
import { x, y } from z
.....
export default Component
main.js
const Component = React.lazy(() => import('Component.js')
Here the main.js chunk will not include code any code from z or
any of the code from Component.js and its unique dependencies
https://webpack.js.org/guides/code-splitting/#dynamic-imports
https://create-react-app.dev/docs/code-splitting/#appjs

Resources