const tableContext = React.createContext();
const tableContextProvider = ({children}) => {
const [isTableOne, setTableOne] = useState(false);
const [isTableTwo, setTableTwo] = useState(false);
useEffect(() => {
setTableValues();
}, []);
function setTableValue() {
setTableOne(result.IS_TABLE_ONE); // true
setTableTwo(result.IS_TABLE_TWO); // false
}
return (<TableContext.Provider value={{isTableOne, isTableTwo}}>{children}
{isMetaTable? <LoadingComponent /> : null}
</TableContext.Provider>);
};
If the context is from MetaTable, I would like to show a loading screen
{isMetaTable? <LoadingComponent /> : null}
How can I pass a value to the context to set the isMetaTable value to true?
<Switch>
<Route exact path='/route1'><TableContextProvider><MetaTable /></TableContextProvider></Route>
<Route exact path='/route2'><TableContextProvider><Table2 /></TableContextProvider></Route>
<Route exact path='/route3'><TableContextProvider><Table3 /></TableContextProvider></Route>
<Redirect from='/' to='/route1' />
</Switch>
Related
function App() {
const [token, setToken] = useState();
useEffect(() => {
const auth = localStorage.getItem('auth_token');
setToken(auth);
}, [token]);
return (
<div>
<FullNavBar />
<Routes>
<Route path='/login' element={<Login />}></Route>
<Route path='/register' element={<Register />}></Route>
<Route path='/forgot_password' element={<ForgotPassword />}></Route>
<Route element={<ProtectedRoutes />}>
<Route path='/home' element={<Home />}></Route>
<Route path='/active_lottery' element={<Activelottery />}></Route>
</Route>
</Routes>
</div>
);
}
export default App;
Protected Routes:
import { Navigate, Outlet } from 'react-router';
const authorization = () => {
const token = localStorage.getItem('auth_token');
return token ? true : false;
};
const ProtectedRoutes = () => {
const isAuth = authorization();
return isAuth ? <Outlet /> : <Navigate to='/login' />;
};
export default ProtectedRoutes;
How can I make that navbar to render after changing the page?
After I'm trying to log in I'm redirected to the homepage but I still have the previous NavBar,is re-rendering after I'm refreshing the page.
Login redirect:
const onSubmitHandler = async (event) => {
event.preventDefault();
await axios
.post(
'url/dev/user/login',
loginForm,
)
.then((response) => {
localStorage.setItem(
'auth_token',
response.data.AuthenticationResult.AccessToken,
);
toast.success('Your in now');
setTimeout(() => {
navigate('/home');
}, 1000);
})
.catch((err) => {
console.log(err);
toast.error(err.response.data.message);
});
};
and in the fullNavBar component I want to update the pages from navBar.
export default function FullNavBar() {
const [menuOpen, setMenuOpen] = useState(false);
const [auth, setAuth] = useState(false);
const token = localStorage.getItem('auth_token');
useEffect(() => {
if (token) {
console.log(token);
setAuth(true);
}
}, [auth]);
return (
<div className="bg-gradient-to-r from-yellow-400 to-yellow-500">
<Navbar token={auth} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
{menuOpen && <MobileMenu>{navLinks(auth)}</MobileMenu>}
</div>
);
}
const navLinks = token => {
let pages = ['About', 'Contact', 'Login'];
if (token) {
pages = ['Buy', 'Tickets', 'History', 'Profile'];
}
return pages.map(page =>
page === 'Profile' ? (
<button key={page} className="inline-block">
{' '}
<img
className="rounded-full w-11 h-11"
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQhumf_G7azRo-qCcnB533PPwZx386EK1cozzMAMEtW3A&s"
></img>
</button>
) : page === 'Tickets' ? (
<a
key={page}
className="no-underline text-gray-800 font-semibold hover:text-gray-500 relative bottom-4"
href={`${page.toLowerCase()}`}
>
100 {page}
</a>
) : (
<a
key={page}
className="no-underline text-gray-800 font-semibold hover:text-gray-500 relative bottom-4"
href={`${page.toLowerCase()}`}
>
{page}
</a>
),
);
};
everything is working fine after I'm refreshing the page but when it's redirected from login to home the navBar component isn't called
The issue here is that the code is trying to use localStorage actively as the source of truth. It is only to be used for longer-term storage of your React state. Use the token state in App as the active source of truth and pass the token state and state updater function down as props to the relevant components that care.
Example:
App
Use a state initializer function to set/provide the initial token state from localStorage. Use the useEffect hook to persist token state changes to localStorage.
function App() {
const [token, setToken] = useState(() => {
// Initialize from localStorage
return JSON.parse(localStorage.getItem('auth_token'));
});
useEffect(() => {
// Persist state changes to localStorage
localStorage.setItem('auth_token', JSON.stringify(token));
}, [token]);
return (
<div>
<FullNavBar token={token} /> // <-- pass token as prop
<Routes>
<Route
path='/login'
element={<Login setToken={setToken} />} // <-- pass state updater function
/>
<Route path='/register' element={<Register />} />
<Route path='/forgot_password' element={<ForgotPassword />} />
<Route
element={<ProtectedRoutes isAuth={!!token} />} // <-- pass isAuth prop
>
<Route path='/home' element={<Home />} />
<Route path='/active_lottery' element={<Activelottery />} />
</Route>
</Routes>
</div>
);
}
ProtectedRoutes
import { Navigate, Outlet } from 'react-router';
const ProtectedRoutes = ({ isAuth }) => {
return isAuth ? <Outlet /> : <Navigate to='/login' replace />;
};
export default ProtectedRoutes;
Login
Destructure and access the passed setToken callback and call it and pass the new token value to update the state in App.
const Login = ({ setToken }) = { // <-- access state updater function
...
const onSubmitHandler = async (event) => {
event.preventDefault();
await axios
.post('url/dev/user/login', loginForm)
.then((response) => {
setToken(response.data.AuthenticationResult.AccessToken); // <-- save token to state
toast.success('You're in now');
setTimeout(() => {
navigate('/home');
}, 1000);
})
.catch((err) => {
console.log(err);
toast.error(err.response.data.message);
});
};
...
};
FullNavBar
Access the passed token prop. Instead of using the token to set some local auth state just consume the token directly.
export default function FullNavBar({ token }) { // <-- access token prop
const [menuOpen, setMenuOpen] = useState(false);
return (
<div className="bg-gradient-to-r from-yellow-400 to-yellow-500">
<Navbar
token={token} // <-- pass to Navbar
menuOpen={menuOpen}
setMenuOpen={setMenuOpen}
/>
{menuOpen && (
<MobileMenu>
{navLinks(token)} // <-- pass to navLinks utility
</MobileMenu>
)}
</div>
);
}
const navLinks = token => {
let pages = ['About', 'Contact', 'Login'];
if (token) {
pages = ['Buy', 'Tickets', 'History', 'Profile'];
}
return pages.map(page =>
....,
);
};
When you signup/ login/ refresh the page the login page flashes just before going to the correct page. I tried putting a loader in but all it does is do a loader animation before it flashes then goes to correct page. Any idea how to get this to not happen?
function App(){
const [user, setUser] = useState();
const [loading, setLoad] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(getAuth(), setUser, setLoad(false))
return () => {unsubscribe()}
}, [])
const AuthenticatedRoute = ({children}) => {
let isAuthenticated;
if(user !=null){
isAuthenticated = true;
}else{
isAuthenticated = false;
}
if(!loading){
return isAuthenticated? children: <Navigate to="/signin"/>
}else{
return <Loading/>
}
}
const UnauthenticatedRoute = ({children}) => {
let isAuthenticated;
if(user !=null){
isAuthenticated = true;
}else{
isAuthenticated = false;
}
if(!loading){
return !isAuthenticated? children: <Navigate to="/home"/>
}else{
return <Loading/>
}
}
return(
<Router>
<div className="App">
{
<Routes>
<Route exact path="/" element={<UnauthenticatedRoute><PreHome/></UnauthenticatedRoute>}/>
<Route path='/home' element={<AuthenticatedRoute><Home/></AuthenticatedRoute>} />
<Route exact path="/signin" element={<UnauthenticatedRoute><Signin/></UnauthenticatedRoute>} />
<Route exact path="/signup" element={<UnauthenticatedRoute><Signup/></UnauthenticatedRoute>} />
</Routes>
}
</div>
</Router>
)
}
Sign Out code:
This one line has been working for signing out
<button onClick={() => signOut(getAuth())}>Sign Out</button>
Sign In code:
async function OnFormSubmit(e){
e.preventDefault();
const auth = getAuth();
try{
isLoading(true);
await signInWithEmailAndPassword(auth, email, pw)
isLoading(false);
}catch(err){
console.log(err)
}
}
Issues
The loading state is only cleared when there's an error in onAuthStateChanged.
The route protector components are declared inside another React component. This is anti-pattern. When new "instances" of these components are declared each time the parent App component rerenders, it will unmount/mount the component's subReactTree.
The components don't wait for the user's authentication status to resolve before deciding to redirect or render the routed content.
Solution
Move the route protector components out on their own. Render an Outlet for nested routes to be rendered into. This allows you to render these as layout routes instead of individual wrapper components.
Use a proper "loading" state. Use undefined user state as the "loading state".
Example:
import { Outlet, Navigate } from 'react-router-dom';
const AuthenticatedRoute = ({ user }) => {
if (user === undefined) return <Loading />;
return user
? <Outlet />
: <Navigate to="/signin" />;
};
const UnauthenticatedRoute = ({ user }) => {
if (user === undefined) return <Loading />;
return user
? <Navigate to="/home" />
: <Outlet />;
};
...
function App(){
const [user, setUser] = useState();
useEffect(() => {
const unsubscribe = onAuthStateChanged(getAuth(), setUser);
return unsubscribe;
}, []);
return(
<AuthContextProvider>
<Router>
<div className="App">
<Routes>
<Route element={<UnauthenticatedRoute user={user} />}>
<Route path="/" element={<PreHome />} />
<Route path="/signin" element={<Signin />} />
<Route path="/signup" element={<Signup />} />
</Route>
<Route element={<AuthenticatedRoute user={user} />}>
<Route path='/home' element={<Home />} />
</Route>
</Routes>
</div>
</Router>
</AuthContextProvider>
);
}
This is my app.js where all the routes
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route element={<PrivateRoute />}>
<Route exact path="/dashboard" element={<Dashboard />} />
<Route exact path="/payment" element={<Payment />} />]
</Route>
<Route exact path="/login" element={<Login />} />
</Routes>
</Router>
This is my PrivateRoute component
function PrivateRoute({ fetchMe, ...props }) {
const [type, setType] = useState("xxxxx");
const isAuthenticated = localStorage.getItem("authToken");
const navigate = useNavigate();
const [lodar, setLodar] = useState(false);
useEffect(() => {
setLodar(false);
if (isAuthenticated) {
(async () => {
const {
value: { user },
} = await fetchMe();
console.log({ data: user.step1 });
if (user.step === 1) {
navigate("/payment");
}
setLodar(false);
})();
}
}, []);
return (
<Spin indicator={antIcon} spinning={lodar}>
{isAuthenticated ? (
<>
<Header type={type} setType={setType} />
<Outlet context={[type, setType]} />
</>
) : (
<Navigate to="/login" />
)}
</Spin>
);
}
export default PrivateRoute;
So what I want to do here is to always redirect the user to the "/payment" after signup. and if the user again comes after login then it will again redirect it to the payment page so for that I am keeping a flag in my database user.step and checking by api call on the PrivateRoute component.
The issue is it loads the "/dashboard" page before the fetchUser api call which should not happen and show some lodar before. How can I do that?
Is there any better approach doing this since I always have to make an api call?
Kindly help!!!
Assuming <Spin indicator={antIcon} spinning={lodar}> is conditionally rendering either a loading spinner/indicator or the wrapped children then I think the issue is just the initial lodar state value. It doesn't appear the lodar state is ever toggled true.
I suggest starting with an initially true state so the component doesn't immediately render the Outlet or redirect when the component mounts, prior to any auth checks happening via the useEffect hook.
Example:
function PrivateRoute({ fetchMe, ...props }) {
const [type, setType] = useState("xxxxx");
const isAuthenticated = localStorage.getItem("authToken");
const navigate = useNavigate();
const [lodar, setLodar] = useState(true); // <-- initially true
useEffect(() => {
setLodar(true); // <-- toggle true when starting async logic
if (isAuthenticated) {
(async () => {
const {
value: { user },
} = await fetchMe();
console.log({ data: user.step1 });
if (user.step === 1) {
navigate("/payment");
}
setLodar(false); // <-- clear loading when complete
})();
}
}, []);
return (
<Spin indicator={antIcon} spinning={lodar}>
{isAuthenticated ? (
<>
<Header type={type} setType={setType} />
<Outlet context={[type, setType]} />
</>
) : (
<Navigate to="/login" />
)}
</Spin>
);
}
Is it compulsory to use the of react-router in the App.js?
I'm trying to use the react router in my app and I'm stuck at something.
I have a component called ProductList.jsx. In that component I want to define the path of Cart.jsx component using . I definitely want to define path of the Cart.jsx inside of ProductList.jsx because it does take some props from the ProductList.jsx.
When I tried to define the path it throws an error like "Error: Invariant failed: You should not use <Link> outside a <Router>"
Including the code below
App.jsx
import Home from "./pages/Home";
const App = () => {
return (
<>
<Home />
</>
)
};
export default App
Home.jsx
const Home = () => {
return (
<div>
<Navbar />
<Slider/>
<Categories/>
<PopularProducts/>
<ProductList/>
<Footer/>
</div>
);
};
ProductList.jsx
const ProductList = () => {
const [mobileOpen, setMobileOpen] = useState(false);
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const [order, setOrder] = useState({});
const [errorMessage, setErrorMessage] = useState('');
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};
const handleUpdateCartQty = async (lineItemId, quantity) => {
const response = await commerce.cart.update(lineItemId, { quantity });
setCart(response.cart);
};
const handleRemoveFromCart = async (lineItemId) => {
const response = await commerce.cart.remove(lineItemId);
setCart(response.cart);
};
const handleEmptyCart = async () => {
const response = await commerce.cart.empty();
setCart(response.cart);
};
const refreshCart = async () => {
const newCart = await commerce.cart.refresh();
setCart(newCart);
};
const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
try {
const incomingOrder = await commerce.checkout.capture(checkoutTokenId, newOrder);
setOrder(incomingOrder);
refreshCart();
} catch (error) {
setErrorMessage(error.data.error.message);
}
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
const handleDrawerToggle = () => setMobileOpen(!mobileOpen);
const user= ''
return (
<Container>
<Navbar />
<Router>
<div style={{ display: 'flex' }}>
<CssBaseline />
<Switch>
<Route path="/login" >
{user ? <Redirect to="/"/> : <Login/> }
</Route>
<Route path="/register" >
<Register />
</Route>
<Route path="/products">
<Products products={products} onAddToCart={handleAddToCart} handleUpdateCartQty />
<Footer/>
</Route>
<Route exact path="/cart">
<Cart cart={cart} onUpdateCartQty={handleUpdateCartQty} onRemoveFromCart={handleRemoveFromCart} onEmptyCart={handleEmptyCart} />
</Route>
<Route path="/checkout">
<Checkout cart={cart} order={order} onCaptureCheckout={handleCaptureCheckout} error={errorMessage} />
</Route>
</Switch>
</div>
</Router>
<Cart cart={cart} onUpdateCartQty={handleUpdateCartQty} onRemoveFromCart={handleRemoveFromCart} onEmptyCart={handleEmptyCart} />
<Footer/>
</Container>
);
};
export default ProductList;
"Error: Invariant failed: You should not use <Link> outside a
<Router>"
It's compulsory to render all Link, Switch, and Route components within a routing context. From your snippet I don't see a Router rendered until well into the ProductList component. I suspect the "link outside a router" issue is from your Navbar component being outside the Router component.
Move the Router to wrap the entire app. Remove all other extraneous routers, if there are any. You need only one single router to provide a routing context to the entire app.
import { BrowserRouter as Router } from 'react-router-dom';
const App = () => {
return (
<Router>
<Home />
</Router>
)
};
...
const ProductList = () => {
...
return (
<Container>
<Navbar />
<div style={{ display: 'flex' }}>
<CssBaseline />
<Switch>
... routes ...
</Switch>
</div>
...
</Container>
);
};
hello
I am trying to make a menu toggle, where I have a variable with false as initial value, using react createContext and useContext hook, I set the initial state as true
// useMenu Context
import React, { useContext, useState } from 'react'
export const useToggle = (initialState) => {
const [isToggled, setToggle] = useState(initialState)
const toggle = () => setToggle((prevState) => !prevState)
// return [isToggled, toggle];
return { isToggled, setToggle, toggle }
}
const initialState = {
isMenuOpen: true,
toggle: () => {},
}
export const MenuContext = React.createContext(initialState)
const MenuProvider = ({ children }) => {
const { isToggled, setToggle, toggle } = useToggle(false)
const closeMenu = () => setToggle(false)
return (
<MenuContext.Provider
value={{
isMenuOpen: isToggled,
toggleMenu: toggle,
closeMenu,
}}>
{children}
</MenuContext.Provider>
)
}
export default MenuProvider
export const useMenu = () => {
return useContext(MenuContext)
}
so If true it will show the Menu if false it will show the Div where there a div
App.js
const { isMenuOpen } = useMenu()
//the providder
<MenuProvider>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />{' '}
</MenuProvider>
and when I add an onclick event the NavLink button of the menu to close it it does not work
Menu
const { closeMenu } = useMenu()
// return statement
{paths.map((item, i) => {
return (
<MenuItem
key={i}
link={item.location}
svg={item.icon}
path={item.name}
command={item.command}
onClick={closeMenu}
/>
)
})}
where did I go wrong
Issue
I suspect the issue is in App where you've a useMenu hook outside the MenuProvider used in App. This useMenu hook is using a MenuContext context but in the absence of a provider it instead uses the default initial context value.
const initialState = {
isMenuOpen: true,
toggle: () => {},
};
export const MenuContext = React.createContext(initialState);
export const useMenu = () => {
return useContext(MenuContext)
};
React.createContext
const MyContext = React.createContext(defaultValue);
Creates a Context object. When React renders a component that
subscribes to this Context object it will read the current context
value from the closest matching Provider above it in the tree.
The defaultValue argument is only used when a component does not
have a matching Provider above it in the tree. This default value can
be helpful for testing components in isolation without wrapping them.
Solution
Since I doubt you want to run/provide more than one menu provider I believe the solution is to move MenuProvider out of and wrap App to provide the context you are updating by nested components.
App.jsx
const { isMenuOpen } = useMenu();
...
<>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />
</>
index.jsx (?)
import App from './App.jsx';
...
//the provider
<MenuProvider>
<App />
</MenuProvider>