React context with useState not updating - reactjs

I am trying to use the react context api. I am experiencing an issue though where the context/state value is not updating. I have no idea why this is happening and have looked at numerous threads but have found that nothing works for me.
Here is the code:
curtain-context.js
For creating the contexts and exporting them:
const CurtainContext = createContext({
curtainVisible: false,
setCurtainVisible: (value) => {}
});
export function CurtainContextProvider(props) {
const [curtainVisible, setCurtainVisible] = useState();
function setCurtainVisibleHandler(value) {
setCurtainVisible(value);
console.log(value);
}
const context = {
curtainVisible: curtainVisible,
setCurtainVisible: setCurtainVisibleHandler
};
return (
<CurtainContext.Provider value={context}>
{props.children}
</CurtainContext.Provider>
);
}
export default CurtainContext;
App.js
The main application code which is surrounded by the context provider:
<Layout>
<CurtainContextProvider>
<Routes>
<Route element={<HomePage/>} path='/' exact/>
<Route element={<HomePage/>} path='/home' exact/>
<Route element={<ServicesPage/>} path='/services' exact/>
<Route element={<ProductsPage/>} path='/products' exact/>
<Route element={<ContactPage/>} path='/contact' exact/>
<Route element={<LoginPage/>} path='/login' exact/>
</Routes>
</CurtainContextProvider>
</Layout>
MainNavigation.js
The place where I want to use the context value to render something if curtainVisible is true:
import { NavLink } from 'react-router-dom';
import classes from './MainNavigation.module.css';
import React, {useContext, useState} from "react";
import { useLocation } from "react-router";
import MobileCurtain from "../ui/MobileCurtain";
import CurtainContext from "../../store/curtain-context";
function MainNavigation() {
var curtainContext = useContext(CurtainContext);
const { pathname } = useLocation();
const activeClass = ({isActive}) => (isActive ? classes.active : classes.inactive);
const activeUserClass = ({paths = ['/login', '/settings']}) => (paths.includes(pathname) ? classes.active : classes.inactive);
function handleBarsClicked() {
curtainContext.setCurtainVisible(true);
}
return (
<div className={classes.menu}>
<ul>
<li className={classes.textLinkBars}><button className={classes.iconButton} onClick={handleBarsClicked}><FontAwesomeIcon icon={faBars} className={classes.bars}/></button></li>
{ curtainContext.curtainVisible ? <MobileCurtain/> : null}
<li className={classes.textLink}><NavLink to="/" className={activeClass}>Home</NavLink></li>
<li className={classes.textLink}><NavLink to="/services" className={activeClass}>Services</NavLink></li>
<li className={classes.textLink}><NavLink to="/products" className={activeClass}>Products</NavLink></li>
<li className={classes.textLink}><NavLink to="/contact" className={activeClass}>Contact</NavLink></li>
</div>
</ul>
</div>
);
}
export default MainNavigation;

Only components that are descendants of the Provider can use context value.
In your example, MainNavigation isn't a descendant of CurtainContextProvider hence the issue.
You set your initial value to
{
curtainVisible: false,
setCurtainVisible: (value) => {}
}
which didn't helped, because this (value) => {} was run instead of setCurtainVisibleHandler.
I would suggest using undefined as an initial value of context
Also, hooks like this can help prevent the issue like yours:
const useCurtainContext = () => {
const context = useContext(CurtainContext);
if (!context) {
throw new Error('`useCurtainContext` have to be used inside `CurtainContextProvider`')
}
return context
}

Related

Defined but not used in React

I'm trying to build my homepage but I'm getting the below errors in my terminal and my react page is blank. Any suggestions on what I change to fix these errors? Thanks in advance
Line 4:8: 'Cart' is defined but never used
Line 7:27: 'Router' is defined but never used
Line 7:50: 'Link' is defined but never used
Line 17:8: 'page' is assigned a value but never used
Line 28:9: 'addToCart' is assigned a value but never used
import React, { useState } from "react";
import "./Homepage.css";
import Shop from "./Shop";
import Cart from "./Cart";
import About from "./About";
import ContactUs from "./ContactUs";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
const PAGE_SHOP = "shop";
const PAGE_CART = "cart";
const PAGE_HOMEPAGE = "home";
export default function Homepage() {
const [cart, setCart] = useState([]);
const [page, setPage] = useState(PAGE_SHOP);
const navigateTo = (nextPage) => {
setPage(nextPage);
};
const getCartTotal = () => {
return cart.reduce((sum, { quantity }) => sum + quantity, 0);
};
const addToCart = (product) => {
let newCart = [...cart];
let itemInCart = newCart.find((item) => product.name === item.name);
if (itemInCart) {
itemInCart.quantity++;
} else {
itemInCart = {
...product,
quantity: 1,
};
newCart.push(itemInCart);
}
setCart(newCart);
};
return (
<div className="Header">
<header>
<button onClick={() => navigateTo(PAGE_CART)}>
Go to Cart ({getCartTotal()})
</button>
<button onClick={() => navigateTo(PAGE_SHOP)}>Shop</button>
</header>
<router>
<Routes>
<Route path="/" element={<PAGE_HOMEPAGE />} />
<Route path="/About" element={<About />} />
<Route path="/Shop" element={<Shop />} />
<Route path="/ContactUs" element={<ContactUs />} />
</Routes>
</router>
</div>
);
}
You have set const PAGE_HOMEPAGE = 'home';
If you look at the following line
<Route path="/" element={<PAGE_HOMEPAGE />} />
You are passing a string instead of an element.
Replace PAGE_HOMEPAGE with a react component that you would like to render on path '/'
To remove the warnings, just remove the code mentioned in those warnings, if you don't intend on using it.
If you do plan on using it, ignore the warnings for now and they will go away once the code is used.

Directing user to '/en/' or '/fr/' path upon loading page with React?

I'm wondering how I can direct the user to the correct language path for their location upon loading the page in react?
I know I can detect their locaalized langauge with navigator.language. But then how do I use that to route them to a page immediately?
You could have a functional component as a intro component in your app that will read that location and route to the correct language using the useHistory hook like this:
import { useHistory } from "react-router-dom";
function HomePage() {
const history = useHistory();
React.useEffect(() => {
// Use navigator.language here
history.push("/en");
}, []);
return (
<div>
Loading...
</div>
);
}
You can always read more about it in the React Router docs and in the React Hooks docs.
You should read https://reactrouter.com/web/api/
navigator.language returns the browser's language,
and I assume that it is what you really want
I did it with basename that is one of BrowserRouter properties
import {
BrowserRouter,
NavLink,
Redirect,
Route,
Switch
} from "react-router-dom";
import "./styles.css";
export default function App() {
const lang = navigator.language.split("-")[0];
return (
<div className="App">
<BrowserRouter basename={lang}>
<Menu />
<Switch>
<Redirect exact from="/" to="/home" />
<Route path="/" exact component={Home} />
<Route path="/home" exact component={Home} />
<Route path="/about" exact component={About} />
<Redirect to="/home" />
</Switch>
</BrowserRouter>
</div>
);
}
export const Menu = () => {
return (
<>
<NavLink to="./home">Home</NavLink>
<span> / </span>
<NavLink to="./about">About</NavLink>
</>
);
};
export const Home = () => {
return <h1>Home</h1>;
};
export const About = () => {
return <h1>About</h1>;
};

React router: Expected "state" to be defined when moving to /

I have a React app which doens't have a route to / setup, so I placed a redirect inside the Switchcomponent, so whenever the user tries to access home they are redirected to the UserHome component.
The Switch set-up is as follows:
const AppRoutes = () => (
<Switch>
<Redirect
exact
from="/"
to={ROUTES.CITIZEN}
/>
<Route
exact
path="/login"
component={Login}
/>
<AuthenticatedRouteRedirect
path={ROUTES.CITIZEN}
component={UserHome}
/>
<AuthenticatedRouteRedirect
path={ROUTES.ADMIN_REPORT_LIST}
component={reportList}
/>
<Route
path="/404"
component={ErrorView}
/>
<Route
component={ErrorView}
/>
</Switch>
And AuthenticatedRouteRedirect as follows:
const AuthenticatedRouteRedirect = ({
component: Component, path = '', exact = false, ...rest
}) => {
const { user } = useAuth();
return (
<Route
path={path}
exact={exact}
render={() => (user
? <Component {...rest} />
: <Redirect to="/login" />)}
/>
);
};
export default AuthenticatedRouteRedirect;
And my UserHome component:
const Dashboard = () => (
<>
<Navbar />
<Container>
<ActionMenu />
</Container>
</>
);
where ActionMenu component uses a custom hook called useReportsLocations, which its implementation is:
import React from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { getMapPolygons, mexicoMap } from 'core/map-locations';
const useReportsLocations = (selectedGroup) => {
const { push } = useHistory();
const { state, municipality } = useParams();
const locationData = React.useMemo(() => {
const currentSelection = {
country: mexicoMap,
state,
municipality,
group: selectedGroup,
};
return getMapPolygons(currentSelection);
}, [municipality, selectedGroup, state]);
React.useEffect(() => {
if (!locationData?.stateData
|| !Object.keys(locationData?.stateData?.municipality
|| {}).includes(municipality)) {
push('/404');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [municipality]);
return {
locationData,
state,
municipality,
};
};
export default useReportsLocations;
Here's a little demo on CodeSandBox
But when trying to access /, this error message is displayed with the next stacktrace, sorry if it's too large:
What is the cause for this to happen? And how to fix it?
I'm having a bad time trying to figure out whatever the cause is, but simply I can't.
Thank you for your answer and replies.
Pd
I finally figured out what's happening.
After I read the docs, as Redirect leads me to a route where some params are required, but I don't provide any ones to to (as seen in the ROUTES file), it has only the placeholders of {ROUTES.CITIZEN} route, and path-to-regexp#^1.7.0 will complaint that it has been passed nothing and coudn't not resolve to anything.
This behaviour is expected.

Why react-router Route attr render remount not rerender when component update?

My main intention is to rewrite the Switch component, and then realize the caching of the component through the display, instead of destroying it every time.
// App
const App = () => {
return (
<MySwitch>
<Route path="path1" component={<Component1>}>
<Route path="path2" component={<Component2>}>
</MySwitch>
)
}
// MySwitch
const cacheRouteMap = {}
const MySwitch = props => {
return (
<Route
path="*"
render={
context => {
const location = props.location || context.location
let element
let match
let currentMatchPath
React.Children.forEach(props.children, child => {
// eslint-disable-next-line no-eq-null
if (match == null && React.isValidElement(child)) {
element = child
const path = child.props.path || child.props.from
currentMatchPath = path
match = path ? matchPath(location.pathname, {...child.props, path}) : context.match
}
})
if (!cacheRouteMap[currentMatchPath]) {
cacheRouteMap[currentMatchPath] = React.cloneElement(element, {
location,
computedMatch: match,
})
}
return Object.values(cacheRouteMap).map(d => {
const {path} = d.props
return <div style={{display: path === currentMatchPath ? 'block' : 'none'}}>{d}</div>
})
}
}
/>
)
}
The above code can run, but id the upper layer is re-render, the Route components render method inside MySwitch will remount.
Below is my test the render method re-mount every time.
react-router: 5.2.0;
react-router-dom: 5.2.0;
code:
import React, {useState, useEffect} from 'react'
import {HashRouter as Router, Route, Switch, Redirect} from 'react-router-dom'
const Demo = () => {
useEffect(() => {
console.log('Demo did mount........')
}, [])
return 1111
}
const App = () => {
const [visible, setVisible] = useState(false)
return (
<div>
<div onClick={() => setVisible(!visible)}>button</div>
<div style={{display: visible ? 'block' : 'none'}}>xxxxx</div>
<Router>
<Switch>
<Route path="/" render={() => <Demo />} />
<Redirect to="/" />
</Switch>
</Router>
</div>
)
}
when App rerender, the Demo is re mount not rerender. What should I do the Demo rerender with render function? thanks!😋
Have you tried using this
<Route exact path="/"><Demo /></Route>
When you are using render={() => <Demo />}, you are basically using an anonymous function which is creating a new instance of the Demo component.
With this approach you can pass your own custom param, as well as use the hooks like useLocation given by react-router.
Sample usage
<Route exact path="/">
<App someProp={1} />
</Route>
function App(props) {
let location = useLocation();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<h2>Value of someProp is {props.someProp} </h2>
<h2>Pathname is {location.pathname}</h2>
</div>
);
}
Check this Code Sandbox for a working sample.
You just need to mount the <Demo /> directly.
<Route path="/" exact>
<Demo />
</Route>

React Router Pass Param to Component

const rootEl = document.getElementById('root');
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route exact path="/">
<MasterPage />
</Route>
<Route exact path="/details/:id" >
<DetailsPage />
</Route>
</Switch>
</BrowserRouter>,
rootEl
);
I am trying access the id in the DetailsPage component but it is not being accessible. I tried
<DetailsPage foo={this.props}/>
to pass parameters to the DetailsPage, but in vain.
export default class DetailsPage extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="page">
<Header />
<div id="mainContentContainer" >
</div>
</div>
);
}
}
So any idea how to pass the ID on to the DetailsPage ?
I used this to access the ID in my component:
<Route path="/details/:id" component={DetailsPage}/>
And in the detail component:
export default class DetailsPage extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
</div>
)
}
}
This will render any ID inside an h2, hope that helps someone.
If you want to pass props to a component inside a route, the simplest way is by utilizing the render, like this:
<Route exact path="/details/:id" render={(props) => <DetailsPage globalStore={globalStore} {...props} /> } />
You can access the props inside the DetailPage using:
this.props.match
this.props.globalStore
The {...props} is needed to pass the original Route's props, otherwise you will only get this.props.globalStore inside the DetailPage.
Since react-router v5.1 with hooks:
import { useParams } from 'react-router';
export default function DetailsPage() {
const { id } = useParams();
}
See https://reacttraining.com/blog/react-router-v5-1/
Use render method:
<Route exact path="/details/:id" render={(props) => (
<DetailsPage id={props.match.params.id}/>
)} />
And you should be able to access the id using:
this.props.id
Inside the DetailsPage component
In addition to Alexander Lunas answer ...
If you want to add more than one argument just use:
<Route path="/details/:id/:title" component={DetailsPage}/>
export default class DetailsPage extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
<h3>{this.props.match.params.title}</h3>
</div>
)
}
}
Use the component:
<Route exact path="/details/:id" component={DetailsPage} />
And you should be able to access the id using:
this.props.match.params.id
Inside the DetailsPage component
This is for react-router-dom v6 (I highly suggest using functional components for this)
It's somewhat painful for react-router-dom to keep changing syntax and rules. But here goes nothing.
You can use both useParams and useSelector to solve this
import { useParams } from 'react-router';
import { useSelector } from 'react-redux';
const Component = () => {
const { id } = useParams(); //returns the :id
const page = useSelector((state) => state.something[id]); //returns state of the page
return <div>Page Detail</div>;
}
export default Component;
BUT, the problem persist when you also have an action creator and you want to pass it as a props in connect function
export const connect(mapStateToProps, mapDispatchToProps)(Component)
since we are using useParams, it won't be passed to mapStateToProps that we created
const mapStateToProps = (state, ownProps) => {
console.log(ownProps) //wont recognize :id
//hence
return {
someReducers: state.someReducers[id] //would return an error: 'id' is not defined
};
};
on the other hand, you can't entirely ignore the connect function since you need mapDispatchToProps to work with your component.
The workaround to this is to create a Higher Order Component withRouter function yourself. This was a deprecated react-router-dom helper.
//make this
import { useParams, useLocation, useNavigate } from 'react-router';
import { connect } from 'react-redux';
import { yourActionCreator } from '../actionCreator';
const withRouter = (Child) => {
return (props) => {
const location = useLocation();
const navigation = useNavigate();
const params = useParams();
return (
<Child
{...props}
params={params}
navigate={navigate}
location={location}
/>
);
};
};
const Component = () => {
// your component...
return <div> Page Detail </div>
};
export mapStateToProps = (state, ownProps) => {
console.log(ownProps) // would contain the :id params
return {
//something
}
};
const mapDispatchToProps = {
yourActionCreator
}
export withRouter(connect(mapStateToProps, mapDispatchToProps)(Component));
Here's typescript version. works on "react-router-dom": "^4.3.1"
export const AppRouter: React.StatelessComponent = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path="/problem/:problemId" render={props => <ProblemPage {...props.match.params} />} />
<Route path="/" exact component={App} />
</Switch>
</BrowserRouter>
);
};
and component
export class ProblemPage extends React.Component<ProblemRouteTokens> {
public render(): JSX.Element {
return <div>{this.props.problemId}</div>;
}
}
where ProblemRouteTokens
export interface ProblemRouteTokens {
problemId: string; }
Another solution is to use a state and lifecycle hooks in the routed component and a search statement in the to property of the <Link /> component. The search parameters can later be accessed via new URLSearchParams();
<Link
key={id}
to={{
pathname: this.props.match.url + '/' + foo,
search: '?foo=' + foo
}} />
<Route path="/details/:foo" component={DetailsPage}/>
export default class DetailsPage extends Component {
state = {
foo: ''
}
componentDidMount () {
this.parseQueryParams();
}
componentDidUpdate() {
this.parseQueryParams();
}
parseQueryParams () {
const query = new URLSearchParams(this.props.location.search);
for (let param of query.entries()) {
if (this.state.foo!== param[1]) {
this.setState({foo: param[1]});
}
}
}
render() {
return(
<div>
<h2>{this.state.foo}</h2>
</div>
)
}
}
FOR version 6 ( 2022 )
Note: using useParams you can easily get your params in your component.
look at the example below
import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from "./compo/home";
import About from "./compo/about";
import Login from "./compo/login";
import "./styles.css";
const App = () => {
return (
<Router>
<div className="container">
<Link to="/home">Home</Link>
<Link to="/about">About</Link>
<Link to="/login">Login</Link>
</div>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/login/:name" element={<Login />} />
</Routes>
</Router>
);
};
export default App;
Login Component
import { useParams } from "react-router-dom";
const Login = () => {
let { name } = useParams();
return <h1>i am {name ? <b>{name}</b> : "login"}</h1>;
};
export default Login;
if you are using class component, you are most likely to use GSerjo suggestion. Pass in the params via <Route> props to your target component:
exact path="/problem/:problemId" render={props => <ProblemPage {...props.match.params} />}
In the latest version of (react-router-dom#6.3.0), you can do it like this:
<Route path="path" element={<YourComponent type="simple" />} />
Here, type is the input passed to YourComponent
I was working on react-router-dom version 6.3.0 and above solution didn't resolve my problem. Then I use something like this and it worked:
<Route exact path='/payment-status/:userId/:orderId' element={<PaymentStatus/>}/>
And on PaymentStatus.js page I did like this:
import { useParams } from 'react-router-dom'
export const PaymentStatus = () => {
let {userId, orderId}=useParams()
return (
<div>
<h2>order ID : {orderId}</h2>
<h2>user ID : {userId}</h2>
</div>
)
}
It worked for me. I hope it may help someone. Thanks!
try this.
<Route exact path="/details/:id" render={(props)=>{return(
<DetailsPage id={props.match.params.id}/>)
}} />
In details page try this...
this.props.id
Simple example with Class, HoC and Router v5
package.json
"react-router-dom": "5.3.1",
"react-router": "5.3.1",
"#types/react-router-dom": "5.3.3",
// YourComponent.tsx
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
export interface PathParams {
id: string;
}
export interface Props extends RouteComponentProps<PathParams> {}
export interface State {}
class YourComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
console.log(props.match.params) // { id: 1 }
// TypeScript completions
console.log(props.match.params.id) // 1
}
render() {
return <></>;
}
}
export default withRouter(YourComponent);
// App.tsx
import './App.css';
import React from 'react';
import { Route, Switch, Router } from 'react-router-dom';
import YourComponent from './YourComponent';
function App(): JSX.Element {
return (
<Router>
<Switch>
<Route
path="/details/:id"
component={() => <YourComponent />}
/>
</Switch>
</Router>
);
}
export default App;

Resources