Using useHistory on React - reactjs

I am a beginner in React and trying to learn things by myself. I have this code that I'd like to navigate to the Login page using useHistory but I can't seem to make it work. Hope you can help me. Here is my code below:
import { useHistory } from "react-router-dom";
const App = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('./container/Login');
}
return (
<div>
<button className='btn' text='User Login' onClick=.
{MoveToLogin}>Login</button>
</div>
);
}
export default App;

First you need to add Provider 'Router' which is imported from 'react-router-dom'
Define routes and corresponding components in routing file.
You can only use history inside the children of Provider Router.
Navigate to route using history.push('/login'). Don't provide relative path of component file here. Use the route you want to show in browser url

I have played around and did more research about useHistory. I was able to make it work. It has navigated to a new component. Please find my solution below. Hope it can help others with the same kind of issue.
import UserLogin from "./pages/UserLogin";
const ButtonLogin = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('/pages/UserLogin');
}
return (
<div><button className='btn btn-primary' onClick={MoveToLogin}>Login</button></div>
);
}
const App = () => {
return (
<div>
<Router>
<Route path="/pages/UserLogin" exact component={UserLogin} />
<Route path="/" exact component={ButtonLogin} />
</Router>
</div>
);
}
export default App;
Refer to this answer as well for further information.
Cannot read property 'push' of undefined for react use history

I hope you have the routes defined for the path specified
<Router>
<Route path="/container/Login" exact component={LoginComponent} />
</Router>
The dot is not needed in your move to login function.
import { useHistory } from "react-router-dom";
const App = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('/container/Login'); // Here you don't need dot ./container
}
return (
<div>
<button className='btn' text='User Login' onClick=.
{MoveToLogin}>Login</button>
</div>
);
}
export default App;

Related

dynamically change the basename in react BrowserRouter with API call

Recently in a project I've been working on had a requirement to change the content based on the user's location, so we used a third-party service to get the browsing location from IP address and according to the country they are brows from, we have to change the content and the URL of the site to something like this, domainName.com/us so the /us part should be dynamic and I used basename prop in BrowserRouter for that to maintain consistency. but the problem is I can't change the basename dynamically, since the country code comes from an API, it takes some time to fetch, and obviously, it's an async call. state variable I used is updating after data fetch, but the URL in the browser didn't update.
basically, by default, the site will load with the content according to the user's location, but if the user types let's say /uk in the URL after the domain, the site content should be changed according to that and maintain the URL in sitewide operations
here's an example site you can refer
raileurope.com
you can type, let's say as a example /fr after the domain in that site, then the site content will be changed to french, if user type /it the content will be change to italian, like wise.
a new approach to fulfilling this requirement is also welcomed. Thanks in advance.
what I have right now.
react-router-dom: 5.2.0
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
const App = () => {
const [browsFrom, setBrowsFrom] = useState('');
useEffect(() => {
axios.get('https://api64.ipify.org')
.then((res) => {
axios.get(`http://www.geoplugin.net/json.gp?ip=${res.data}`)
.then((response) => {
console.log(response);
setBrowsFrom(`/${response.data.geoplugin_countryCode}`);
}).catch((e) => {
console.log(e);
setBrowsFrom('');
})
}).catch((e) => {
console.log(e);
setBrowsFrom('');
});
}, []);
return (
<Router basename={browsFrom}>
<Switch>
<Route path="/" component={HomeComponent} exact={true} />
<Route
path="/termsAndConditions"
component={TermsPage}
exact={true}
/>
<Route
path="/aboutUs"
component={AboutUsPage}
exact={true}
/>
</Switch>
</Router>
);
}
You can create a history object dynamic based on your sub-path and pass it as a history in <Router history={history}>. You can try this solution.
import React, { useState, useMemo, useEffect } from "react";
import { render } from "react-dom";
import { Router, Link } from "react-router-dom";
import { createBrowserHistory } from "history";
// To demonstrate the ability to have a dynamic
// base route, change the url path and add something
// like: "/test/123/" then refresh the page and
// navigate - then, refresh again.
const Header = () => (
<header>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/sub">Submenu</Link>
</li>
</ul>
</nav>
</header>
);
const Root = () => {
const [path, setPath] = useState("my-app");
const history = useMemo(() => {
return createBrowserHistory({ basename: path });
}, [path]);
useEffect(() => {
console.log(history);
if (path !== "my-app") {
history.replace("/");
}
}, [history, path]);
return (
<Router history={history}>
Current basename – {path}
<Header />
<button
onClick={() => {
setPath("asif");
}}
>
change base path{" "}
</button>
</Router>
);
};
render(<Root />, document.getElementById("root"));

Why browser back button returns a blank page with the previous URL

On pressing the browser back button why does an empty-blank page is displayed instead of the component that I'd visited before? Only the URL is getting changed to the previous one. Using React Router v5
That is really frustrating, how can I fix this ?
SignUp.js
render() {
return (
<div className='signUp-div'>
<Header />
<Router history={history}>
<div className='form-div'>
<Redirect to='/signup/mobile' /> // Default page
<Switch>
<Route exact path={'/signup/mobile'} component={MobileNum} />
<Route exact path={'/signup/idnumber'}>
<IdentNumber setPersonalID={this.props.setUserNumber} />
</Route>
<Route exact path={'/signup/password'}>
<CreatePass
setIfSignUp={this.props.setIfSignUp}
/>
</Route>
</Switch>
</div>
</Router>
</div>
);
}
IdentNumber.js
const IdentNumber = ({setPersonalID}) => {
const handleCheckID = () => {
history.push('/signup/password');
}
return (
<div className='form-div'>
<button
onChange={(event) => onChangeHandler(event)}
> Page password</button>
</div>
);
};
export default IdentNumber;
Did I explain it right ?
Thanks
From the code sandbox link, I've observed a few things that could potentially cause this issue.
Update your imports from import { Router } from "react-router-dom";
to
import { BrowserRouter as Router } from "react-router-dom";
<BrowserRouter> uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.
The routes will remain the same. You're using react-router-dom v5.2.0, you could use useHistory to get the history object. useHistory simplified the process of making components route-aware.
With my changes: https://codesandbox.io/s/suspicious-pine-5uwoq
We don't need exact key for all routes other than the default "/" when it is enclosed in Switch and placed in the end. But exact matches /signup/mobile and /signup/* as same. Switch renders only one route and whichever route is matched first.
An example project for reference.
And if you want to handle the back button event yourself, follow the below examples.
In a function component, we can handle the back button press by listening to the history object.
import { useHistory } from 'react-router-dom';
const Test = () => {
const history = useHistory();
useEffect(() => {
return () => {
if (history.action === "POP") {
}
};
}, [history])
}
listen to history in useEffect to find out if the component is unmounted. history.listen lets us listen for changes to history.
Example:
import { useHistory } from 'react-router-dom';
const Test = () => {
const history = useHistory();
useEffect(() => {
return history.listen(location => {
if (history.action === 'POP') {
}
})
}, [])
}
react-router-dom now has Prompt,
import { Prompt } from "react-router-dom";
<Prompt
message={(location, action) => {
if (action === 'POP') {
// back button pressed
}
return location.pathname.startsWith("/test")
? true
: `Are you sure you want to go to ${location.pathname}?`
}}
/>

How do I route between pages in Embedded React App?

Background:
I am trying to create some links in my embedded Shopify app.
I understand that I cannot use the simple <a> tag due to the fact that Shopify embedded apps are rendered as iframes.
I made some headway with this tutorial, but I am stuck: https://theunlikelydeveloper.com/shopify-app-bridge-react-router/
What I am trying to do:
I have 3 pages (index.js, dashboard.js, and support.js). I would like to allow the user to navigate from one page to another (with links and/or buttons).
My Code:
By following the tutorial above, I've gotten this far:
// index.js
import { Page, Frame } from "#shopify/polaris";
const Index = () => {
return (
<Page>
<Frame>
{/* LINK TO DASHBOARD PAGE*/}
{/* LINK TO SUPPORT PAGE */}
</Frame>
</Page>
);
};
export default Index;
// routes.js
import React from "react";
import { Switch, Route, withRouter } from "react-router";
import { ClientRouter, RoutePropagator } from "#shopify/app-bridge-react";
function Routes(props) {
const { history, location } = props;
return (
<>
<ClientRouter history={history} />
<RoutePropagator location={location} />
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/support">
<Support />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</>
);
}
export default withRouter(Routes);
// link.js
import React from "react";
import { Link as ReactRouterLink } from "react-router";
const IS_EXTERNAL_LINK_REGEX = /^(?:[a-z][a-z\d+.-]*:|\/\/)/;
function Link({ children, url = "", external, ref, ...rest }) {
if (external || IS_EXTERNAL_LINK_REGEX.test(url)) {
rest.target = "_blank";
rest.rel = "noopener noreferrer";
return (
<a href={url} {...rest}>
{children}
</a>
);
}
return (
<ReactRouterLink to={url} {...rest}>
{children}
</ReactRouterLink>
);
}
export default Link;
Additional:
I believe I'm supposed to implement the following code somewhere, but I don't see how it fits into the picture of navigating between pages with a link or button.
<AppProvider linkComponent={Link}>
{/* App content including your <Route> components */}
</AppProvider>
Link to Shopify Docs: https://polaris.shopify.com/components/structure/app-provider#section-using-linkcomponent
At this time of building embedded app you can make client-side navigation using app-bridge utilities as referred to in this answer
You just need to edit _app file and consider making client-side navigation from your components(can't use a normal Link)
import {useEffect} from 'react';
import Router, { useRouter } from "next/router";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
function RoutePropagator () {
const router = useRouter();
const { route } = router;
const app= useAppBridge();
useEffect(() => {
app.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return app && route ? (
<ShopifyRoutePropagator location={route} />
) : null;
}
Then use this component in your _app file
_app.tsx
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props as any;
return (
<PolarisProvider i18n={translations}>
<ShopifyBridgeProvider
config={{
apiKey: API_KEY,
host,
forceRedirect: true,
}}
>
<RoutePropagator />
<ApolloClientProvider Component={Component} {...pageProps} />
</ShopifyBridgeProvider>
</PolarisProvider>
);
}
}
Now you've subscribed for routing events in _app file, we just require to make client-side navigation right in your pages
import {useAppBridge} from '#shopify/app-bridge-react';
import { Redirect } from '#shopify/app-bridge/actions';
function IndexPage(props) {
const app = useAppBridge();
return (
<>
<div>{'you are in main page'}</div>
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/dashboard'
}));
}}>
{'to dashboard'}
</div>
</>
);
}
And for going back to the main page / route, I've found that it trigger an oauth again if not provided with the shop name, so we will use the shop query params for that
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/?shop=<shop-name>.myshopify.com'
}));
}}>
{'to main'}
</div>

useParams hook returns undefined in react functional component

The app displays all photos <Photo> in a grid <PhotoGrid>, then once clicked, a function in <Photo> changes URL with history.push, and Router renders <Single> based on URL using useParams hook.
PhotoGrid -> Photo (changes URL onClick) -> Single based on URL (useParams).
I must have messed something up, becouse useParams returns undefined.
Thanks for all ideas in advanced.
App.js
class App extends Component {
render() {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
</>
)
}
}
export default App;
Photogrid.js
export default function PhotoGrid() {
const posts = useSelector(selectPosts);
return (
<div>
hi
{/* {console.log(posts)} */}
{posts.map((post, i) => <Photo key={i} i={i} post={post} />)}
</div>
)
}
in Photo I change URL with history.push
const selectPost = () => {
(...)
history.push(`/view/${post.code}`);
};
Single.js
import { useParams } from "react-router-dom";
export default function Single() {
let { id } = useParams();
console.log("id:", id) //returns undefined
return (
<div className="single-photo">
the id is: {id} //renders nothing
</div>
)
}
When using useParams, you have to match the destructure let { postId } = useParams(); to your path "/view/:postId".
Working Single.js
import { useParams } from "react-router-dom";
export default function Single() {
const { postId } = useParams();
console.log("this.context:", postId )
return (
<div className="single-photo">
{/* render something based on postId */}
</div>
)
}
You should use the same destructure as mentioned in your Route path. In this case, you should have written :
let { postID } = useParams();
I will mention two more mistakes which someone could make and face the same problem:
You might use Router component in place of Route component.
You might forget to mention the parameter in the path attribute of the Route component, while you would have mentioned it in the Link to component.
Ensure the component where you call useParams() is really a child from <Route>
Beware of ReactDOM.createPortal
const App = () => {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
<ComponentCreateWithPortal /> // Impossible to call it there
</>
)
}
You have to check API that you are using. Sometimes it's called not just id. That's why useParams() do not see it

ReactJS TypeError: Cannot read property 'push' of undefined

I build my Apps and learn ReactJS
My code error until now, i try to search on internet and forums but can't fix.
Here my code :
logout(){
localStorage.removeItem("token");
localStorage.clear();
if(localStorage.getItem('token') === null){
this.props.history.push('/login')
}
}
Error : TypeError: Cannot read property 'push' of undefined
Please Help Me
My guess is your logout is within a component which is not a Route it self.
By that what I mean is you're not doing <Route path="/" component={ComponentThatHasLogoutMethod} />
Only the components that are used as Route has history prop.
if you need those in some other nested Component you can use the withRouter Higher order component from react-router-dom
export default withRouter(ComponentThatHasLogoutMethod)
This will pass the history prop to your component and you'll not get null.
Solution Example with react hooks:
import { useHistory } from "react-router-dom";
function HomeButton() {
const history = useHistory();
function handleClick() {
localStorage.removeItem("token")
localStorage.clear()
if(!localStorage.getItem('token')){
history.push("/login")
}
}
return (
<button type="button" onClick={handleClick}>
Login
</button>
);
}
More info you can find in docs
https://reacttraining.com/react-router/web/api/Hooks/usehistory
I have faced similar issue while creating custom portal to show model like logout/login.
const App = () => {
const [modal] = useSelector(({ modal }) => [modal])
return (
<>
<Portal modal={modal} />
<BrowserRouter>
<Routes />
</BrowserRouter>
</>
);
}
export default App;
Correct way-
Portal must me child of BrowserRouter.
const App = () => {
const [modal] = useSelector(({ modal }) => [modal])
return (
<>
<BrowserRouter>
<Portal modal={modal} />
<Routes />
</BrowserRouter>
</>
);
}
For more reference see Router.
We have two solutions for this problem -:
Replace function declaration with fat arrow notation declaration i.e -:
logout = () => {
localStorage.removeItem("token");
localStorage.clear();
if(localStorage.getItem('token') === null){
this.props.history.push('/login');
}
}
We can bind logout function in the constructor -:
constructor(props){
super(props);
this.logout = this.logout.bind(this);
}

Resources