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

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.

Related

How can I know if specific parent React component exists?

So I've got a component that is dependent on the context of BrowserRouter to hook into
<BrowserRouter>
<...>
<.../>
<.../>
<MyRedirectComponent/>
<.../>
</...>
</BrowserRouter>
I would love to simply include BrowserRouter inside my MyRedirectComponent that way I wouldn't need to wrap it all the time.
const MyRedirectComponent = () => {
const browserRouterParentExists = // I dunno
return browserRouterParentExists ? (
<NormalStuff/>
) : (
<BrowserRouter>
<NormalStuff/>
</BrowserRouter>
)
}
Is correctly populating browserRouterParentExists possible?
Writing conditional logic that tries to access parent component is an anti-pattern in React IMO and I don't think there is a public API you can use for that purpose.
I didn't get what you are trying to achieve but BrowserRouter is defined as
A <Router> that uses the HTML5 history API (pushState, replaceState
and the popstate event) to keep your UI in sync with the URL.
and most of the time you only need one BrowserRouter in your app in a top level component like App considering its usage. So wrapping a component with it and using that component throughout your app is not quite reasonable. If you are trying to redirect user to another route based on some condition, you can use any data coming from props, state, Context API or a state management lib. like Redux etc. to implement your logic and render Redirect component together with that conditional logic.

Using React Router functionality outside the rendering

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

React Router v4 replace history when component is unmounted

I have a app which has the Following Components
|__Base - /home/Base
|__Try - /home/Base/:try
|__Report - /home/Base/:try/report
Base is the Starting screen where the user hits a button and clicks on Try and after trying some things he hits submits which generates reports which has some back end interactions and when the data is fetched it loads the Reports.
So what i want is when the user hits the back button from the Reports Page he should not land on the Try page but on the Base page .
For that to work i went through the react router documentation and was trying to use history.replace on componentWillUnmount for Reports Page
this.props.history.replace(`/home/Base`, {
pathname: `/home/Base`,
search: null,
state: {
isActive: true
}
}, null);
In case the Report Page is FullyLoaded and i press the back button it works but calls the Try Render Method too and then takes me to the Base Page , But in case of Reports Not fully Loaded and i press the back button while the loading spinner is in progress it goes to base page still but also mounts and unmounts the TRY component.
What am i missing here , what causes it to mount/unmount or render the previous component and then load the base component even though i replace the history stack ?
Reason
Related with this issue
React v16, changing routes, componentWillMount of the new route is called before componentWillUnmount of the old route
Update:
Solution (checked, update online demo later)
Use react-router-last-location to get previous pathname
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
import { LastLocationProvider } from 'react-router-last-location';
<BrowserRouter>
<LastLocationProvider>
<Switch>
...
</Switch>
</LastLocationProvider>
</BrowserRouter>
Check previous pathname in componentWillMount, if it's from certain page, push a new pathname to route.
componentWillMount() {
const { history, lastLocation } = this.props;
if (lastLocation?.pathname === '/home/Base/:try/report') {
history.push({pathname: '/home/Base'});
}
}
You can use the HOC they provide or write it yourself refer to the lib's source to reduce the dependencies
import { withLastLocation } from 'react-router-last-location';
interface Props {
lastLocation: any,
history: any,
}
export const YourComponent = withLastLocation(connect(
...
))
In this way you can redirect all the routing process from certain pages without mount current page, no matter you clicked a back button or clicked the back in your browser.

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)

React router with browserHistory goes to server on every URL change

I am doing something like:
<Router history={browserHistory}>{routes}</Router>
When I do above whenever URL in address bar changes call is going to server but this is not what I want, I want first time page to load from server but after that whenever route change component should load in client side only. Am I missing something here?
In client side I am doing something like :
ReactDOM.render(
<Provider store={app.store}>
<Router history={browserHistory}>{routes}</Router>
</Provider>,
document.getElementById("app")
);
and my routes look like:
const routes = (
<Route path="/" component={DJSAppContainer}>
<Route path="page" component={DJSPage}>
<Route path="/page/:pageName" component={PageContainer} />
</Route>
</Route>
);
Now whenever I do location.href = "/page/xyz" it goes to server and load the content.
You shouldn't change location.href directly. You should send the new path to React using:
ReactRouter.browserHistory.push(newPath);
If you have anchor tags, you should use the <Link> component mentioned in #ahutch's answer.
React router exposes as props the history used in the current views. See their docs for all the props that are injected here.
If you want to redirect from DJSAppContainer or any of the two children views DJSPage or PageContainer you could do it by accessing the history property:
const {history} = this.props;
history.pushState('/path/to/new/state');
If on the other hand you do some fancy stuff and want to redirect from outside the components, try this (taken from react router docs)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
This two approaches assume you are trying to redirect based on some complex logic, not as a consequence of a click event. If you just want to handle click events, try using the <Link/> component of react-router.
It seems that the history property is now deprecated, and seems to be replaced by the router context property as seen in the implementation of the link component. Basically, if you really want your code to be future proof, you should ask for the contextType:
contextTypes: {
router: object
},
And then use it from the context of the component:
this.context.router.push('/some/path')
Assuming you're using node on the back-end, make sure that you have it set up according to the react-router docs.
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Also, when you're linking between components and you want to ensure that you are using react-router for the routing instead of the server, make sure to use Link. Hope that answers your question.
I was having a similar issue, while hashHistory worked without issue browserHistory would always load from the server. I got things working by remembering to call preventDefault(). Here is my function:
handleSubmit: function (e) {
e.preventDefault();
var username = this.usernameRef.value;
this.usernameRef.value = '';
this.context.router.push(`/profile/${username}/`);
}
handleSubmit is called on a form onSubmit.

Resources