How to use react-router-dom with Context API V6? - reactjs

I am changing the value in PC component but it is not reflected in the BR1 component. If I don't use react-router-dom, everything works fine, but I need the routes.
App.js code
import React, { createContext, useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import BR1 from './Components/BR1';
import PC from './Components/P_c1'
import BR from './Components/BR';
export const BRcontext = createContext();
function App() {
const [value, setValue] = useState(false)
return (
<div>
<BRcontext.Provider value={{value, setValue}}>
<Router>
<Routes>
<Route path='/PC' element={<PC/>} />
<Route path='/BR1' element={<BR1/>} />
<Route path='/BR' element={<BR/>} />
</Routes>
</Router>
</BRcontext.Provider>
</div>
);
}
export default App;
PC code
import React, { useContext } from 'react'
import './Profile.css';
import { BRcontext } from '../App';
export default function Profile() {
const {value, setValue} = useContext(BRcontext);
return (
<div>
<div className='container mt-5'>
<div className='row'>
<div>
<h3 className='mt-5'>Send Request</h3>
<button className='btn btn-success mt-3 ps-3 pe-3' onClick={()=>{setValue(true)}}>Request</button>
</div>
</div>
</div>
</div>
)
}
BR1 code
import React, { useContext } from 'react'
import BR from './BR'
import { BRcontext } from '../App'
import { Link } from 'react-router-dom';
export default function BR1() {
const {value} = useContext(BRcontext);
// let navigate = useNavigate();
return (
<div>
{console.log(value)} //this remains false
{value ? <Link to="/BR"/>: console.log('hello there!')}
</div>
)
}
In BR1 code, I want the value to become true when a button in the PC component is clicked
Link - https://codesandbox.io/s/great-star-bzhuvw?file=/src/App.js

It seems there's no way to navigate from /PC to /BR1 unless changing the browser URL directly, and by doing this, you lose the current context value because it's in memory. If you intend to keep this behaviour, you should consider persisting the context value every time you change it and initialize it with the previously persisted one.
An example using the browser's local storage:
// Helper function to read the storaged value if it exists
function getPersistedValue() {
const serializedValue = localStorage.getItem('value')
try {
if (!serializedValue) {
throw new Error('No previously persisted value found')
}
return JSON.parse(serializedValue)
} catch {
return false
}
}
// Using the helper function to initialize the state
const [value, setValue] = useState(getPersistedValue())
// Synchronizing the persisted value on local storage with the in-memory one
useEffect(() => {
localStorage.setItem('value', JSON.stringify(value))
}, [value])
If you want, I forked your Code Sandbox and applied these changes: https://codesandbox.io/s/router-context-forked-uqhzye.

Related

React & Typescript Issue: trigger elements with InsertionObserver using props and manage them in other component

Small premise: I'm not a great Typescript expert
Hi everyone, I'm working on my personal site, I decided to develop it in Typescript to learn the language.
My component tree is composed, as usual, of App.tsx which render the sub-components, in this case Navbar.jsx and Home.jsx.
Below is the App.jsx code:
import './App.css';
import { BrowserRouter as Router, useRoutes } from 'react-router-dom';
import Home from './components/Home';
import Navbar from './components/Navbar';
import { useState } from 'react';
function App(){
const [navbarScroll,setNavbarScrool]=useState(Object)
const handleLocationChange = (navbarScroll : boolean) => {
setNavbarScrool(navbarScroll)
return navbarScroll
}
const AppRoutes = () => {
let routes = useRoutes([
{ path: "/", element: <Home handleLocationChange={handleLocationChange}/> },
{ path: "component2", element: <></> },
]);
return routes;
};
return (
<Router>
<Navbar navbarScroll={navbarScroll}/>
<AppRoutes/>
</Router>
);
}
export default App;
Here, instead, the Home.jsx code:
import { useInView } from 'react-intersection-observer';
import HomeCSS from "../styles/home.module.css"
import mePhoto from "../assets/me.png"
import { useEffect, useState } from 'react';
interface AppProps {
handleLocationChange: (values: any) => boolean;
}
export default function Home(props: AppProps){
const { ref: containerChange , inView: containerChangeIsVisible, entry} = useInView();
useEffect(()=>{
props.handleLocationChange(containerChangeIsVisible)
//returns false at first render as expected
console.log("Home "+containerChangeIsVisible)
},[])
return(
<>
<div className={`${ HomeCSS.container} ${containerChangeIsVisible? HomeCSS.container_variation: ''}`}>
<div className={HomeCSS.container__children}>
{/* when i scroll on the div the css change (this works)*/}
<h1 className={`${ HomeCSS.container__h1} ${containerChangeIsVisible? HomeCSS.container__h1_variation: ''}`}>My<br/> Name</h1>
<p>Computer Science student.</p>
</div>
<img src={mePhoto} className={HomeCSS.image_style}/>
</div>
<div ref={containerChange} style={{height:800,background:"orange"}}>
<p style={{marginTop:20}}>HIII</p>
</div>
</>
)
}
And Navbar.jsx:
import NavbarCSS from "../styles/navbar.module.css"
import acPhoto from "../assets/ac.png"
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
interface NavbarScroolProp{
navbarScroll:boolean
}
export default function Navbar(props:NavbarScroolProp){
const [scrollState,setScrollState]=useState(false)
const [pVisible,setpVisible] = useState('')
useEffect(()=>{
setTimeout(() => {
setpVisible("")
}, 3000)
setpVisible("100%")
},[])
//returns false also when should be true
console.log(props.navbarScroll)
return (
<>
{/*the props is undefined so the css doesn't change, i need to do this*/}
<nav className={`${props.navbarScroll?NavbarCSS.nav__variation:NavbarCSS.nav}`}>
<div className={NavbarCSS.nav_row}>
<div className={NavbarCSS.nav_row_container}>
<img src={acPhoto} className={NavbarCSS.image_style}/>
<p className={NavbarCSS.p_style} style={{maxWidth: pVisible}}>My name</p>
</div>
<div className={NavbarCSS.nav_row_tagcontainer}>
<Link className={NavbarCSS.nav_row_tag} to="/"> Home</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> About</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> Contact</Link>
</div>
</div>
</nav>
</>
);
}
In my application I want to change the background color whenever the div referring to the InsertionObserver ( I use "useInView" hook , from :https://github.com/thebuilder/react-intersection-observer) is displayed. The problem is that the div in question is in the Home.jsx component and I need to change the color of the divs in the navbar as well when the div in Home is triggered(or other components in case I need to in the future).
The question is: How can I dynamically trigger DOM elements of other components (to then perform certain operations) using the InsertionObserver ?
As you can see from the code I tried to create Props, but everything returns undefined and doesn't involve any changes.
I've tried without useEffect, without using the useInView hook, passing the object instead of the boolean value, but I can't find any solutions to this problem.
You would be of great help to me.
PS: I would like to leave the Navbar.jsx component where it is now, so that it is visible in all components.
Any advice or constructive criticism is welcome.

Why is my useContext not working in my app and my browser remains loading?

Im trying to use useContext hook to pass variables and functions through different components without lifting them to the main App.js component. I was trying to do this and it seemed to compile correctly but when i go to my browser my app is stucked in a blank page and remains loading.
LoginContext.js: In this component i store the user data in an object using the useState hook and i use jwt_decode to decode the use token and get all the data i need to store.
import React, { createContext, useState } from "react";
import jwt_decode from 'jwt-decode';
const LoginContext = createContext();
export function LoginProvider({children}) {
const [user, setUser] = useState({})
function handleCallbackResponse(response){
var userData = jwt_decode(response.credential); //Token with the login user Data
setUser(userData); //Store user Data
/* console.log(userData) */
document.getElementById('signInDiv').hidden = true;
}
function handleSignOut(event) {
setUser({}) //Empy the user Data
document.getElementById('signInDiv').hidden = false;
}
return(
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
}
export default LoginContext
The i have my Login.js which uses LoginContext: Here i use the user to show the different data of the logged in use and the handleCallbackResponse to do my Login.
import React, { useContext, useEffect } from 'react'
import LoginContext from '../LoginContext';
const Login = () => {
const {user, handleCallbackResponse, handleSignOut} = useContext(LoginContext)
useEffect(()=>{
/*global google*/
google.accounts.id.initialize({
client_id:"My client ID",
callback: handleCallbackResponse
})
google.accounts.id.prompt();
google.accounts.id.renderButton(
document.getElementById('signInDiv'),
{theme: 'outline', size: 'medium'}
)
}, []);
return (
<div>
<div id="signInDiv"></div>
{
//If user objetc is not empty show sign out button
Object.keys(user).length !== 0 &&
<button onClick={(e)=>handleSignOut(e)}>Sign Out</button>
}
{user &&
<div>
<img src={user.picture} alt="" />
<h3>{user.name}</h3>
</div>
}
</div>
)
}
export default Login
App.js:
import './App.css';
import Login from './atoms/Login';
import { BrowserRouter , Routes, Route } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import { LoginProvider } from './LoginContext';
import PrivateRoutes from './utils/PrivateRoutes';
function App() {
return (
<LoginProvider>
<BrowserRouter>
<Routes>
{/* <Route element={<PrivateRoutes/>}>
</Route> */}
<Route exact path="/dashboard" element={<Dashboard/>}/>
<Route path="/" element={<Login/>} />
</Routes>
</BrowserRouter>
</LoginProvider>
);
}
export default App;
For some reason my application runs with no error but in the browser it remains loading with a blank page and im not able to inspect the page.
Instead of:
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
Replace with
<LoginContext.Provider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginContext.Provider>

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

How to provide context from contextApi for a specific set of routes in nextjs and preserve state with routing through linking?

I am using contextApi with nextjs and I'm having some trouble when providing a context just for certain routes. I am able to make the context available for just a few routes, but when I transition from one to the other through linking, I end up losing the state of my application.
I have three files inside my pages folder:
index.tsx,
Dashboard/index.tsx and
SignIn/index.tsx.
If I import the provider inside the files Dashboard/index.tsx and SignIn/index.tsx and go from one page to the other by pressing a Link component from next/link, the whole state is set back to the initial state.
The content of the Dashboard/index.tsx file
import React from 'react';
import Dashboard from '../../app/views/Dashboard';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<Dashboard />
</AuthProvider>
);
export default Index;
This is the contend of the SignIn/index.tsx file:
import React from 'react';
import SignIn from '../../app/views/SignIn';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<SignIn />
</AuthProvider>
);
export default Index;
The views folder is where I create the components that will be rendered.
The content of the file views/SignIn/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const SignIn: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="Dashboard">Go back to Dashboard</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default SignIn;
And the content of the file views/Dashboard/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const Dashboard: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="SignIn">Go back to sign in page</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default Dashboard;
I am able to access the context inside both /Dashboard and /SignIn, but when I press the link, the state comes back to the initial one. I figured out that the whole provider is rerenderized and therefore the new state becomes the initial state, but I wasn't able to go around this issue in a "best practices manner".
If I put the provider inside _app.tsx, I can maintain the state when transitioning between pages, but I end up providing this state to the / route as well, which I am trying to avoid.
I was able to go around this by doing the following, but it really does not seem to be the best solution for me.
I removed the Providers from Pages/SignIn/index.tsx and Pages/Dashboard/index.tsx and used the following snippet for the _app.tsx file:
import React from 'react';
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AuthProvider } from '../contexts/auth';
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const router = useRouter();
const AuthProviderRoutes = ['/SignIn', '/Dashboard'];
return (
<>
{AuthProviderRoutes.includes(router.pathname) ? (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
) : <Component {...pageProps} />}
</>
);
};
export default App;
Does anyone have a better solution?

How do i get data from react hook.?

Im trying to get data , from an API and then consume it from another hook component.
But I have this error
This is my App.js
import React, {useEffect, useState} from 'react';
import {BrowserRouter as Router, Link} from 'react-router-dom';
import MainRouter from "./core/MainRouter";
import {MovieProvider} from "./context/MovieContext";
import axios from 'axios';
function App() {
const [tagCat,setTagCat] = useState([]);
const getMovieInfo = async ()=>{
const res = await axios.get('/get/tag_category');
//for testing
setTagCat(res.data.data)
};
useEffect(()=>{
getMovieInfo();
},[]);
return (
<MovieProvider value={tagCat}>
<Router>
<MainRouter/>
</Router>
</MovieProvider>
);
}
export default App;
MovieContext.js
import React, {useContext} from "react";
const MovieContext = React.createContext({});
const MovieProvider = MovieContext.Provider;
const MovieConsumer = MovieContext.Consumer;
export default MovieContext;
export {MovieProvider,MovieConsumer};
consume from that PageNav component
PageNav.js
import React, {useContext, useEffect, useState} from 'react';
import {Link} from 'react-router-dom';
import MovieContext,{MovieConsumer} from "../../context/MovieContext";
const PageNav = ()=>{
const tagCat = useContext(MovieContext);
const [category,setCategory] = useState([]);
useEffect(()=>{
setCategory(tagCat.main_category);
})
return (
<React.Fragment>
<div>
<header className="content__title">
<h1>Welcome! (Mingalarpar) <small>
Feel Free to use any data , btw we need more suggest from you.
</small></h1>
</header>
<div className="toolbar">
<nav className="toolbar__nav">
**{console.log(category[0])}**
<a className="active" href="#">Following</a>
Groups
</nav>
</div>
</div>
</React.Fragment>
)
};
export default PageNav;
I see the data in devtool,
but i cant map the data
i.e.
category.map()//error
Thank you
The value in ContextProvider is fetched asynchronously and hence won't be available on initial render of the component. If you try to set a specific field from it in state of PageNav component, it will throw you can error
You need to check for its existence before using, also you need not store the value obtained from context in state since you can directly derive it
const PageNav = ()=>{
const tagCat = useContext(MovieContext);
const [category,setCategory] = useState([]);
return (
<React.Fragment>
<div>
{/* othercode */}
<div className="toolbar">
<nav className="toolbar__nav">
{tatCat.main_category && tatCat.main_category.map(() => {})}
<a className="active" href="#">Following</a>
Groups
</nav>
</div>
</div>
</React.Fragment>
)
};
export default PageNav;
You are not passing object in Context provider in App.js. value={tagcat} will be replaced by value={{tagcat}} Try this
<MovieProvider value={{tagCat}}>
<Router>
<MainRouter/>
</Router>
</MovieProvider>

Resources