I'm trying to get my Navbar from antd to re-render using react useContext, where by default 'isAuth' is 'false', and upon user log in, it will be set to 'true'. From my understanding, through useContext, when 'isAuth' is updated, my Navbar should update as well, but it doesn't. Is my understanding in useContext flawed?
Here are the sample codes:
App.js
function App() {
return (
<BrowserRouter>
<UserContextProvider>
<Navbar />
<UserContextProvider>
</BrowserRouter>
);
}
Navbar.js
export default function Navbar(props) {
const userContext = useUserContext();
return(
<Menu theme="dark" mode="horizontal">
<Menu.Item key="1" style={{ float: "right" }}>
{userContext ? (
<Menu.Item
key="2"
onClick={() => nav("/account")}
>
Account
</Menu.Item>
) : (
<Menu.Item
key="3"
onClick={() => nav("/signup")}
>
Login/Signup
</Menu.Item>
)}
</Menu.Item>
</Menu>
);
}
UserContext.js
const UserContext = createContext();
const UpdateUserContext = createContext();
export function useUserContext() {
return useContext(UserContext);
}
export default function UserContextProvider({ children }) {
const [isAuth, setIsAuth] = useState(false);
return (
<UserContext.Provider value={isAuth}>
{children}
</UserContext.Provider>
);
}
Try this approach. Set provider value prop as an object:
return (
<UserContext.Provider value={{isAuth}}>
{children}
</UserContext.Provider>
);
And get isAuth state destructuring your context:
const {isAuth} = useUserContext();
Related
So I currently have a state to toggle dark / light mode on a website with lots of nested components. I have a root App.js:
function App() {
return (
<DarkModeProvider>
<div className="App">
<HomePage />
</div>
</DarkModeProvider>
);
}
The DarkModeProvider is my react context, and in the next component I have a layout where I have my navigation and routing, that is wrapped in the ThemeProvider:
const HomePage = () => {
const { isDarkTheme } = useContext(DarkModeContext);
return (
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) :
createTheme(lightTheme)}>
<DrawerProvider>
<Router>
<Box sx={{ display: "flex" }}>
<Box>
<HomePageInner />
</Box>
<Routes>
<Route path="/inventory" element={<Inventory />} />
<Route path="/orders" element={<Orders />} />
<Route path="/vendors" element={<Vendors />} />
</Routes>
</Box>
</Router>
</DrawerProvider>
</ThemeProvider>
);
};
It works fine, however, I'd like to access the theme context in my "app" class that's in the root App component. If I wrap the DarkModeProvider with the ThemeProvider, I don't have access to the state of the dark / light mode, if I wrap the ThemeProvider with the DarkModeProvider, I lose access to the isDarkTheme state from my context.
Is there a better practice to format this? What I really want is to have a css / style sheet in the source folder as the same level as the app component. I'm unsure how to access my theme provider when it's not in my app component. OR how to have my dark mode state accessible while wrapped inside of the theme provider (or vice versa).
For example my App.CSS:
body {
background-color: theme.primary.palette.main;
/* I would like the body to follow my MUI theme. */
}
a {
color: inherit;
text-decoration: none;
}
Dark Mode Provider:
import { createContext, useState } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeContext.Provider
value={{
isDarkTheme,
changeTheme,
}}
>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;
You can move the ThemeProvider component inside App.js file and have a state there for isDarkTheme which you can then use both for DarkModeProvider and ThemeProvider
function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeProvider value={{ isDarkTheme, changeTheme }}>
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) : createTheme(lightTheme)}>
<div className="App">
<HomePage />
</div>
</ThemeProvider>
</DarkModeProvider>
);
}
Dark Mode Provider:
import { createContext } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children, value }) => {
return (
<DarkModeContext.Provider value={value}>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;
APP.JS
import './App.css';
import {BrowserRouter as Router,Routes,Route,Navigate } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import BookingCar from "./pages/BookingCar";
import "antd/dist/antd.css"
function App() {
return (
<Router>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path="/login" element={<Login/>}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/booking/:id" element={<BookingCar />}></Route>
</Routes>
</Router>
);
}
export default App;
BookingCar.js
import React, {useState,useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import { getAllcars } from "../redux/action/carsAction";
import { useParams } from 'react-router-dom';
import Spinner from "../components/Spinner";
import DefaultLayout from "../components/DefaultLayout";
import { Row, Col} from "antd";
export default function BookingCar({match}){
const { carid } = useParams();
const {cars} = useSelector(state => state.carsReducer)
const {loading} = useSelector(state => state.alertReducer)
const [car, setcar] = useState({})
const dispatch = useDispatch()
useEffect(() => {
dispatch(getAllcars())
if(cars.length>0){
setcar(cars.find(o=>o._id === carid))
}
}, [cars])
return(
<DefaultLayout>
{loading && (<Spinner/> )}
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg"/>
</Col>
</Row>
</DefaultLayout>
)
}
Home.js
import React, {useState,useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import DefaultLayout from "../components/DefaultLayout";
import { getAllcars } from "../redux/action/carsAction";
import { Button, Row, Col} from "antd";
import {Link} from "react-router-dom";
import Spinner from "../components/Spinner";
export default function Home(){
const {cars} = useSelector(state => state.carsReducer)
const {loading} = useSelector(state => state.alertReducer)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getAllcars())
}, [])
return(
<DefaultLayout>
{loading === true && (<Spinner/> )}
<Row justify="center" gutter={16} className="mt-5">
{cars.map(car=>{
return <Col lg={5} sm={24} xs={24}>
<div className="car p-2 bs1 ">
<img alt=""src={car.image} className="carimg"/>
<div className="car-content d-flex align-items-center justify-content-between">
<div>
<p>{car.name}</p>
<p>{car.rentPerHour} Rent Per Hour</p>
</div>
<div>
<button className="btn1 mt-2"><Link to={`/booking/${car._id}`}>Book now </Link></button>
</div>
</div>
</div>
</Col>
})}
</Row>
</DefaultLayout>
)
}
In BookingCar.js i am trying to get the car details like id (image)but i am getting error
So please help me how to solve this issue.
You're trying to access car before it's loaded/applied to state. Check if it exists first before trying to use it -
{car && (
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg"/>
</Col>
</Row>
)}
carid is undefined since it's not a defined route param (path="/booking/:id") so the .find function returns undefined.
You've valid car initial state
const [car, setcar] = useState({});
so you should be able to destructure (car.image & <img alt=""src={car.image} className="carimg"/>) from car without issue. The issue comes later when filtering cars by the cardid route param.
useEffect(() => {
dispatch(getAllcars());
if (cars.length > 0) {
setcar(cars.find(o => o._id === carid));
}
}, [cars]);
array.find can potentially return undefined if no match is found, so the UI should handle that. Route match params are also always strings, so if the _id fields are not also "string" type the strict equality won't work. Try doing a type-safe comparison by converting to strings.
cars.find(o => String(o._id) === carid)
...
return (
<DefaultLayout>
{loading && <Spinner />}
{car && (
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg" />
</Col>
</Row>
)}
</DefaultLayout>
);
Finally, you define the route match param as ":id" but destructure a carid in the component. Ensure the match params match.
If route is:
<Route path="/booking/:id" element={<BookingCar />} />
use const { id } = useParams();
otherwise, update the route param to match the code:
<Route path="/booking/:carid" element={<BookingCar />} />
use const { carid } = useParams();
Before I used react-router-dom and I hadn't any problem and I changed my route without any problem.
But now I bring hook inside of my project and I got a problem.
When I use <NavLink>, my route changes but it does not render anything from my component. When I refresh my page, the component will appear.
My App.js:
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const routes={
route: `/main/symbol/:title/:id`,
exact: true,
component: Symbol,
},
{
route: `/main/symbolDetails/:title/:id`,
exact: true,
component: SymbolDetails,
},
render(){
<Router>
<Switch>
{routes.map((route, k) => (
<Route
key={k}
exact={route.exact}
path={route.route}
component={route.component}
/>
))}
</Switch>
</Router>
}
My Home.js:
(in this component I use navlink for changing my page)
import GridContainer from "../../../components/Grid/GridContainer.js";
import "perfect-scrollbar/css/perfect-scrollbar.css";
// #material-ui/core components
import { makeStyles } from "#material-ui/core/styles";
// core components
import Navbar from "../../../components/Navbars/Navbar.js";
import Sidebar from "../../../components/Sidebar/Sidebar.js";
const useStyles = makeStyles(styles);
export default function Admin({ ...rest }) {
// styles
const classes = useStyles();
const [data, setData] = useState([]);
useEffect(() => getSymbolGroup(), []);
const getSymbolGroup = async () => {
let { data } = await symbolGroup.getSymbolGroup();
setData(data.data);
// console.log("data", data);
};
return (
<div className={classes.wrapper}>
<Sidebar
logoText={"Creative Tim"}
logo={logo}
color={color}
{...rest}
/>
<div className={classes.mainPanel}>
<Navbar
/>
<div className={classes.content}>
<div className={classes.container}>
<GridContainer>
{data &&
data.length &&
data.map((x, key) => {
return (
<div className="Subscrip Bshadow ">
<NavLink
to={`/main/symbol/${x.title}/${x.id}`}
className="a rightanime display awidth flexd"
exact
>
<div className="">
<div className="iconpro display">
<img
className="imgwidth "
src={`http://api.atahlil.com/Core/Download/${x.fileId}`}
/>
</div>
</div>
<div className="">
<p style={{ color: "#a3b0c3", width: "100%" }}>
{x.title}
</p>
</div>
</NavLink>
</div>
);
})}
</GridContainer>
</div>
</div>
)}
I realized my problem.
as I say it was correct when I use in class component.
it is not correct because of my useEffect (hook).
I had to use accolade (I mean {}) after use UseEffect in Home.js component.
home.js
useEffect(() => getSymbolGroup(), []); //it is not correct and I need to refresh my page to render
and the way I had to use useEffect is:
useEffect(() => {
getSymbolGroup();
}, []);
// its correct and does not need to refresh page
Futher to my last question here, I have been trying to map the refs to other routes. The scroll handler is working but ref.current is null. So I am looking for an answer to this dilema. Using no external dependencies, how can I fix this issue?
App.tsx
import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";
const Header = ({ refs }) => {
const location = useLocation();
useEffect(() => {
console.log("location", location.pathname);
switch (location.pathname) {
case "/about":
scrollSmoothHandler(refs.aboutRef);
break;
case "/contact":
scrollSmoothHandler(refs.contactRef);
break;
case "/hero":
scrollSmoothHandler(refs.heroRef);
break;
default:
scrollSmoothHandler(refs.homeRef);
break;
}
}, [location, refs]);
const scrollSmoothHandler = ref => {
console.log("Triggered.");
console.log(ref.current);
//ref.current.scrollIntoView({ behavior: "smooth" });
};
return (
<>
<NavLink to="/hero" activeClassName="selected">
Hero
</NavLink>
<NavLink to="/about" activeClassName="selected">
About
</NavLink>
<NavLink to="/contact" activeClassName="selected">
Contact
</NavLink>
</>
);
};
function App() {
const homeRef = useRef(null);
const heroRef = useRef(null);
const aboutRef = useRef(null);
const contactRef = useRef(null);
return (
<div ref={homeRef} className="App">
<BrowserRouter>
<Header refs={{ aboutRef, contactRef, heroRef, homeRef }} />
<Route
exact
to="/"
refs={{ aboutRef, contactRef, heroRef, homeRef }}
component={Home}
/>
// More routes here.
</BrowserRouter>
</div>
);
}
export default App;
Home.tsx
import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";
const Hero = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Hero Section</h1>
</section>
);
});
const About = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>About Section</h1>
</section>
);
});
const Contact = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Contact Section</h1>
</section>
);
});
function Home(refs) {
const heroRef = useRef(refs.heroRef);
const aboutRef = useRef(refs.aboutRef);
const contactRef = useRef(refs.contactRef);
return (
<Fragment>
<Hero ref={heroRef} />
<About ref={aboutRef} />
<Contact ref={contactRef} />
</Fragment>
);
}
export default Home;
You can find a link to my Code Sandbox: here. Forks are much appreciated.
You cannot pass refs as props to other components with the name prop without using forwardRef on the commponent. You need to assign another name to it in order for it to work, For example innerRefs.
Also to pass on refs as prop to the Route component, make use of render prop method
App.tsx
import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";
const Header = ({ innerRefs }) => {
const location = useLocation();
useEffect(() => {
console.log("location", location.pathname);
switch (location.pathname) {
case "/about":
scrollSmoothHandler(innerRefs.aboutRef);
break;
case "/contact":
scrollSmoothHandler(innerRefs.contactRef);
break;
case "/hero":
scrollSmoothHandler(innerRefs.heroRef);
break;
default:
scrollSmoothHandler(innerRefs.homeRef);
break;
}
}, [location, innerRefs]);
const scrollSmoothHandler = innerRef => {
console.log("Triggered.");
console.log(innerRef.current);
innerRef.current.scrollIntoView({ behavior: "smooth" });
};
return (
<>
<NavLink to="/hero" activeClassName="selected">
Hero
</NavLink>
<NavLink to="/about" activeClassName="selected">
About
</NavLink>
<NavLink to="/contact" activeClassName="selected">
Contact
</NavLink>
</>
);
};
function App() {
const homeRef = useRef(null);
const heroRef = useRef(null);
const aboutRef = useRef(null);
const contactRef = useRef(null);
return (
<div ref={homeRef} className="App">
<BrowserRouter>
<Header innerRefs={{ aboutRef, contactRef, heroRef, homeRef }} />
<Route
exact
to="/"
render={routeProps => (
<Home
{...routeProps}
innerRefs={{ aboutRef, contactRef, heroRef, homeRef }}
/>
)}
/>
// More routes here.
</BrowserRouter>
</div>
);
}
export default App;
Home.tsx
import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";
const Hero = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Hero Section</h1>
</section>
);
});
const About = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>About Section</h1>
</section>
);
});
const Contact = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Contact Section</h1>
</section>
);
});
function Home({ innerRefs }) {
return (
<Fragment>
<Hero ref={innerRefs.heroRef} />
<About ref={innerRefs.aboutRef} />
<Contact ref={innerRefs.contactRef} />
</Fragment>
);
}
export default Home;
Working demo here
I am trying to manage session after successful login while redirecting to some page on form submit.
I would do this usually, in a class component:
componentDidMount() {
if (context.token) {
return <Redirect to="/" />
}
}
But I want to use React hooks, therefore; the following code is not redirecting anywhere:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Switch, Route, Redirect, Link } from "react-router-dom";
es6
const HomePage = props => (
<div>
<h1>Home</h1>
</div>
);
const AboutUsPage = props => {
useEffect(() => {
redirectTo();
}, []);
return (
<div>
<h1>About us</h1>
</div>
);
};
function redirectTo() {
return <Redirect to="/" />;
}
function App() {
return (
<div className="App">
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/us">About us</Link>
</nav>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/us" component={AboutUsPage} />
</Switch>
</BrowserRouter>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working sandbox: https://codesandbox.io/s/blue-river-6dvyv?fontsize=14
I have read that if the hook useEffect() returns a function it will only work when the component unmounts. But it should redirect when the component is being mounted.
Any suggestions? Thanks in advance.
You could set redirect variable on the state and based on it redirect in render:
const AboutUsPage = props => {
const [redirect, setRedirect] = useState(false);
useEffect(() => {
setRedirect(true); // Probably need to set redirect based on some condition
}, []);
if (redirect) return redirectTo();
return (
<div>
<h1>About us</h1>
</div>
);
};
You could have it so that the component selectively renders the page based on whether or not the page is given a token.
const AboutUsPage = ({token}) => (
token ? (
<Redirect to="/" />
) : (
<div>
<h1>About us</h1>
</div>
)
);
However, if you would still like to use context when implementing this with React Hooks you can read up on how to use context with hooks in this article. It shows you how you can incorporate context into React with only a few lines of code.
import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) =>(
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Done with hooks and context, your AboutUsPage component would resemble something like this.
import { useStateValue } from './state';
const AboutUsPage = () => {
const [{token}, dispatch] = useStateValue();
return token ? (
<Redirect to="/" />
) : (
<div>
<h1>About us</h1>
</div>
);
};
import {Redirect, Switch} from "react-router-dom";
and inside Switch....
<Switch>
<Redirect exact from="/" to="/home" />
</Switch>
This solved my issue.