Using React Router functionality outside the rendering - reactjs

I noticed that in all the examples provided by the React Router, it uses the Router objects as part of the UI that gets rendered. But I have a situation where I need to use the Redirect object outside of the rendering code. I have a set of tabs and when the user clicks on a tab, I need to redirect to a different url.
I came across one location in the Router documentation that did show how to use the Router object as part of the normal Javascript code that is not part of the rendering but I could not find it again. In essence I want to do something like this:
function doRedirect() {
return (<Redirect to={"/" + user.username + "/projects"} />);
}
But this will fail to compile. How can I use the Redirect functionality using the angled brackets inside of normal Javascript code?

You could use the useHistory hook, then just history.push(url) or history.replace(url) like this:
import { useHistory } from 'react-router-dom'
const MyComponent = ({ user }) => {
const history = useHistory()
function handleClick() {
history.replace(`/${user.username}/projects`)
}
return (
<button onClick={handleClick}>Redirect to Projects</button>
)
}
This is just an example, but obviously you can use this with quite a lot of flexibility.
See this question for push vs replace

Related

Difference between {Link} and {useNavigate} from 'react-router-dom'

Can anyone please explain the differences between {Link} and {useNavigate} from 'react-router-dom'? I am new to React and I see both {Link} and {useNavigate} are used to navigate through routes. So how are they different?
The difference between the Link (and NavLink and Navigate) components and the navigate function returned by the useNavigate hook is effectively the same difference between Declarative and Imperative programming.
Declarative vs Imperative Programming
Declarative programming is a paradigm describing WHAT the
program does, without explicitly specifying its control flow.
Imperative programming is a paradigm describing HOW the program
should do something by explicitly specifying each instruction (or
statement) step by step, which mutate the program's state.
Imperative programming – focuses on how to execute, defines control flow as statements that change a program state.
Declarative programming – focuses on what to execute, defines program logic, but not detailed control flow.
With the Link (and NavLink and Navigate) components you effectively declare, or defer, what you want to happen, and the component handles getting it done and executing it. These are declarative navigation actions.
Example declarative link:
<Link to="page">Page</Link>
It only specifies the target it wants to get to, but doesn't explain how to get there.
With the navigate function you are explicitly issuing a command to navigate now, immediately. This is an imperative action.
Example imperative link:
<Link
to="page"
onClick={(e) => {
e.preventDefault();
navigate("page");
}}
>
Page
</Link>
This version explicitly explains that if clicked on run this specific logic to navigate to this page.
Note also that Link is a React component and as such it must be rendered into the DOM as part of the return from a React component, whereas the navigate function is a function and can be used in callbacks.
Link is JSX element, it is replace <a>, so it can navigate between route when it clicked without refresh the page.
<Link to='/about'>To About Page</Link>
useNavigate is router hook. Same as Link but it can navigate between route programatically, like onSubmit, it will redirect to anoother page
let navigate = useNavigate();
async function handleSubmit(event) {
event.preventDefault();
await submitForm(event.target);
navigate("/success", { replace: true });
}
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
Link and NavLink are mostly same thing.We use both of them to route pages.But the difference is when we use NavLink we get some advantages like we can design our navigation with active state.Because the NavLink component provides a active class inside it.So we can design our navigation when it is active and we can keep track the active pages.
useNavigate is a hook which returns a function to navigate.But to do this we need to call a navigate function and it declares that how it will work.
Let's say you have some needs to render the some page after checking something (e.g. you have criteria to check whether user have login before or not, so first you check the session of webpage if session is valid or present then then you redirect to user main page otherwise you told that user is log out.) that's time Link and useNavigate use cases shining very much.
code for above thing--->
index.js
root.render(
<BrowserRouter>
<App/>
</BrowserRouter>
);
App.js
const navigate=useNavigate() //remember useNavigate only valid inside
useEffect(()=>{ // BrowserRouter that's why I wrap App.js
// by BrowserRouter in index.js
const key=localStorage.getItem('key');
console.log(key);
if(key===undefined){
navigate('/')
}else{
navigate('/list')
}
},[1])
return <Routes>
<Route path="/" element={<Authentication/>}/>
<Route path="/list" element={<List/>}/>
</Routes>
If I use Link despite of useNavigate then Browser will not complain
but it's not working under the hood. Because Link is only valid till if you include inside webpage(DOM) like anchor or a tag inside html page because Link is same as a tag. But useNavigate is a function or hook what's use anywhere in your code. Because useNavigate not need to add inside DOM.

ComponentDidMount did not fire after using Redirect in react-router

so i was using redirect in react-router-dom, i have 2 pages, home and create when the form in create is done and has been submitted, it will execute the <Redirect> function, and it works, but the ComponentDidMount was not being fired again, i need to reload the page to make the ComponentDidMount to make it start again, here is my code
this is how i redirect in the Create file :
if(this.state.createNew){
return <Redirect to='/home'/>
}
and this is my ComponentDidMount in my Home file :
componentDidMount() {
console.log("hi")
}
the console print hi only on the initial render, when i redirect back to the page it does not fire again, i tried use setState inside the ComponentDidMount but it still not being re rendered.
when i tried using Link to method, it works, the ComponentDidMount is being fired again, but Link to is not what i want, because it does not automatically redirect like the <Redirect> do
i got an error when i try to use the useHistory function :
React Hook "useHistory" is called in function "simpan" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
here is how i use my useHistory :
function simpan(event){
event.preventDefault()
const email = event.target.elements.email.value
const name = event.target.elements.name.value
const admin = event.target.elements.admin.value
const active = event.target.elements.active.value
const history = useHistory()
console.log(email)
history.push('/home')
}
thanks before, anyhelp will be appriciated
instead of <Redirect /> why don't you use history.push('/home'). this will take you to the new route once state is true and call componentDidMount
how to use history:
import { useHistory } from 'react-router-dom'
then inside your component: const history = useHistory()
then whether you need to change the route:
history.push('/home')
If your create is class component just use this.props.history.push("/home") instead of <Redirect> tag.
As create component is your route it will automatically get history object in props.
why to use history instead of redirect tag in your case
Simple example of programatic navigation with react routing Please check console of browser while checking this example

How do I dynamically switch between screens and update the url during search input

I have a search input box located in the header. When a user searches and clicks 'enter', an (callback) event is sent out to all of the relevant components that need to react to the search event, including the components that display the search results. My issue is that the header's search box would be visible on other non-search-result screens, and when I search there's no "clean" way of quickly mounting the search-result screens and displaying the search results (I hope it's not too confusing).
So the question is what type of approaches did you take to solve this issue? I was thinking of relying on window location and relying on React-router to load the search-results screen. Then looking at the query parameter (or path that contains the search query) and then kicking off the search.
Update (for clarity):
Go to https://www.brainyninja.com/podcast/78b7ab84cf98735fbadb41bb634320f8 The body component name is
Now type any other search term in the header's search box and click enter
The body component that displays search results is . I need to navigate to the /search route in order to load the component. The only way I figured out how to do that is by doing a 'window.location = "/search/?query=somesearchquery"' command, which reloads the whole page and negates the point of having an SPA. I don't know of any cleaner way of changing the current body component
Any suggestions?
Found my answer here
https://tylermcginnis.com/react-router-programmatically-navigate/
Had to use withRouter since my header was not rendered by a React Router.
Now, what if the Register component wasn’t being rendered by React Router? (Meaning, we’re not passing Register as a component prop to a Route. Instead, we’re just rendering it ourselves like <Register />). If it’s not rendered by React Router, then we won’t have access to history.push. The team thought of this use case so React Router comes with a handy HOC called withRouter. Going back to our Register code above, by adding withRouter, it would look like this
import {
withRouter
} from 'react-router-dom'
class Register extends React.Component {
handleSubmit = (user) => {
saveUser(user).then(() =>
this.props.history.push('/dashboard')
))
}
render() {
return (
<div>
<h1>Register</h1>
<Form onSubmit={this.handleSubmit} />
</div>
)
}
}
export default withRouter(Register)

Read the current full URL with React?

How do I get the full URL from within a ReactJS component?
I'm thinking it should be something like this.props.location but it is undefined
window.location.href is what you're looking for.
If you need the full path of your URL, you can use vanilla Javascript:
window.location.href
To get just the path (minus domain name), you can use:
window.location.pathname
console.log(window.location.pathname); //yields: "/js" (where snippets run)
console.log(window.location.href); //yields: "https://stacksnippets.net/js"
Source: Location pathname Property - W3Schools
If you are not already using "react-router" you can install it using:
yarn add react-router
then in a React.Component within a "Route", you can call:
this.props.location.pathname
This returns the path, not including the domain name.
Thanks #abdulla-zulqarnain!
window.location.href is what you need. But also if you are using react router you might find useful checking out useLocation and useHistory hooks.
Both create an object with a pathname attribute you can read and are useful for a bunch of other stuff. Here's a youtube video explaining react router hooks
Both will give you what you need (without the domain name):
import { useHistory ,useLocation } from 'react-router-dom';
const location = useLocation()
location.pathname
const history = useHistory()
history.location.pathname
this.props.location is a react-router feature, you'll have to install if you want to use it.
Note: doesn't return the full url.
Plain JS :
window.location.href // Returns full path, with domain name
window.location.origin // returns window domain url Ex : "https://stackoverflow.com"
window.location.pathname // returns relative path, without domain name
Using react-router
this.props.location.pathname // returns relative path, without domain name
Using react Hook
const location = useLocation(); // React Hook
console.log(location.pathname); // returns relative path, without domain name
You are getting undefined because you probably have the components outside React Router.
Remember that you need to make sure that the component from which you are calling this.props.location is inside a <Route /> component such as this:
<Route path="/dashboard" component={Dashboard} />
Then inside the Dashboard component, you have access to this.props.location...
Just to add a little further documentation to this page - I have been struggling with this problem for a while.
As said above, the easiest way to get the URL is via window.location.href.
we can then extract parts of the URL through vanilla Javascript by using let urlElements = window.location.href.split('/')
We would then console.log(urlElements) to see the Array of elements produced by calling .split() on the URL.
Once you have found which index in the array you want to access, you can then assigned this to a variable
let urlElelement = (urlElements[0])
And now you can use the value of urlElement, which will be the specific part of your URL, wherever you want.
To get the current router instance or current location you have to create a Higher order component with withRouter from react-router-dom. otherwise, when you are trying to access this.props.location it will return undefined
Example
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class className extends Component {
render(){
return(
....
)
}
}
export default withRouter(className)
Read this I found the solution of React / NextJs. Because if we use directly used the window.location.href in react or nextjs it throw error like
Server Error
ReferenceError: window is not defined
import React, { useState, useEffect } from "react";
const Product = ({ product }) => {
const [pageURL, setPageURL] = useState(0);
useEffect(() => {
setPageURL(window.location.href);
})
return (
<div>
<h3>{pageURL}</h3>
</div>
);
};
Note:
https://medium.com/frontend-digest/why-is-window-not-defined-in-nextjs-44daf7b4604e#:~:text=NextJS%20is%20a%20framework%20that,is%20not%20run%20in%20NodeJS.
As somebody else mentioned, first you need react-router package. But location object that it provides you with contains parsed url.
But if you want full url badly without accessing global variables, I believe the fastest way to do that would be
...
const getA = memoize(() => document.createElement('a'));
const getCleanA = () => Object.assign(getA(), { href: '' });
const MyComponent = ({ location }) => {
const { href } = Object.assign(getCleanA(), location);
...
href is the one containing a full url.
For memoize I usually use lodash, it's implemented that way mostly to avoid creating new element without necessity.
P.S.: Of course is you're not restricted by ancient browsers you might want to try new URL() thing, but basically entire situation is more or less pointless, because you access global variable in one or another way. So why not to use window.location.href instead?

react-router redirect to a different domain url

I am using react-router for client side routing. I have a button and when some one clicks the button, I want to redirect the user to a different url.
For e.g I want to redirect the user to "http://www.google.com". I used navigation mixin and used this.transitionTo("https://www.google.com"). But when I do this I get this error
Invariant Violation: Cannot find a route named "https://www.google.com".
I can use window.location but is that the right way to go?
As pointed out in the comments to this answer, default way of solving this would be to use anchor element (the a tag) with href attribute that points at the destination URL that you'd like to route the user to. A button that has appearance of a button but behavior or an anchor is pretty much a web anti-pattern. See more info in this answer: https://stackoverflow.com/a/1667512/1460905.
That said, there certainly is a potential scenario when a web app needs to perform some action and only then redirect the user. In this case, if primary action the user takes is submitting some data or really performing an action, and redirect is more of a side-effect, then the original question is valid.
In this case, why not use location property of window object? It even provides a nice functional method to go to external location. See the ref.
So, if you have a component, say
class Button extends Component {
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
then add handleClick that would make the component look like
class Button extends Component {
handleClick() {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
No need to import window since it's global. Should work perfectly in any modern browser.
Also, if you have a component that is declared as a function, you may possibly use the effect hook to change location when state changes, like
const Button = () => {
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
});
return (
<button onClick={() => setClicked(true)}></button>
);
};
You don't need react-router for external links, you can use regular link elements (i.e. <a href="..."/>) just fine.
You only need react-router when you have internal navigation (i.e. from component to component) for which the browser's URL bar should make it look like your app is actually switching "real" URLs.
Edit because people seem to think you can't use an <a href="..." if you need to "do work first", an example of doing exactly that:
render() {
return <a href={settings.externalLocation} onClick={evt => this.leave(evt)}/>
}
async leave(evt) {
if (this.state.finalized) return;
evt.preventDefault();
// Do whatever you need to do, but do it quickly, meaning that if you need to do
// various things, do them all in parallel instead of running them one by one:
await Promise.all([
utils.doAllTheMetrics(),
user.logOutUser(),
store.cleanUp(),
somelib.whatever(),
]);
// done, let's leave.
this.setState({ finalized: true }), () => evt.target.click());
}
And that's it: when you click the link (that you styled to look like a button because that's what CSS is for) React checks if it can safely navigate away as a state check.
If it can, it lets that happen.
If it can't:
it prevents the navigation of occurring via preventDefault(),
does whatever work it needs to do, and then
marks itself as "it is safe to leave now", then retriggers the link.
You can try and create a link element and click it from code. This work for me
const navigateUrl = (url) => {
let element = document.createElement('a');
if(url.startsWith('http://') || url.startsWith('https://')){
element.href = url;
} else{
element.href = 'http://' + url;
}
element.click();
}
As pointed by #Mike 'Pomax' Kamermans, you can just use to navigate to external link.
I usually do it this way, with is-internal-link
import React from 'react'
import { Link as ReactRouterLink} from 'react-router-dom'
import { isInternalLink } from 'is-internal-link'
const Link = ({ children, to, activeClassName, ...other }) => {
if (isInternalLink(to)) {
return (
<ReactRouterLink to={to} activeClassName={activeClassName} {...other}>
{children}
</ReactRouterLink>
)
}
return (
<a href={to} target="_blank" {...other}>
{children}
</a>
)
}
export default Link
Disclaimer: I am the author of this is-internal-link
I had the same issue and my research into the issue uncovered that I could simply use an "a href" tag. If using target="_blank" you should write your link this...
Your Link
I couldn't find a simple way to do that with React Router. As #Mike wrote you should use anchor (<a> tags) when sending the user to external site.
I created a custom <Link> component to dynamically decide whether to render a React-Router <Link> or regular <a> tag.
import * as React from "react";
import {Link, LinkProps} from "react-router-dom";
const ReloadableLink = (props: LinkProps & { forceReload?: boolean }) => {
const {forceReload, ...linkProps} = props;
if (forceReload)
return <a {...linkProps} href={String(props.to)}/>;
else
return <Link {...linkProps}>
{props.children}
</Link>
};
export default ReloadableLink;

Resources