Passing Props through Link in react-router

Perhaps you’re stuck with your data in one component and you’re thinking about whether or how it’s possible to pass that data through react-router’s <Link> component.

This article will talk about

  • 3 ways of passing props through Link
  • when it’s convenient to pass props through Link
  • what pitfalls you should avoid!

There are 3 ways of passing props through Link

  • through url parameters:
    <Link to="/daggala">
  • through state:
    <Link to={{pathname:"/", state: {fromDashboard: true }}}>
  • not through state:
    <Link to={{pathname:"/", fromDashboard: true }}>

I’ll assume that you’re using hooks and v5 or v6 of react router. If you’re not and you find that something is not working, let me know about it, give me your react version and react-router version on Twitter and I’ll see if I can help.

Passing props through through URL params

Passing props through URL params is very convenient when you want a shareable url. For example if you’d want to help your friend finding out how to pass params with react-router, you will be able to share your search results with him: somewebsite.com/courses?q=how+to+pass+params+through+link+in+react+router

There’s an excellent example of URL parameters in the doc: https://reactrouter.com/web/example/url-params

But these URL parameters are limited to only strings. Let’s take a look at how we can pass objects!

Passing props through Link’s state

This is what props passing through <Link> looks like in the docs:

doc-react-router

Passing props through the <Link> component is primarily there to pass props related to the navigation history. That’s because you will only have access to those props by clicking on Link. You won’t have access to those same props if you go directly to that somewebsite.com/courses url in your browser

That means that passing props through Link is an excellent way to give your component some props based on where you were before it got rendered.

Let’s continue with the /courses example from react-router’s documentation

So we have this learning dashboard:

dashboard-example

  • Let’s say that if we’re coming from the dashboard to the /courses page, then we want to show all the courses in a drawer that appears to the right side.

dashboard-drawer

  • In the case we land directly on /courses or if we’re coming from elsewhere in the application we go to the courses main page

courses-page

//App.js

import { BrowserRouter as Router, Route, Link } from "react-router-dom"
import Courses from "./Courses"

const App = () => {
  return (
    <Router>
      <Route path="/courses">
        <Courses />
      </Route>
      <Link
        to={{
          pathname: "/courses",
          state: { fromDashboard: true },
        }}
      >
        Go to courses
      </Link>
    </Router>
  )
}

//Courses.js

import { useLocation } from "react-router-dom"
import Drawer from "components/Drawer"
import PageLayout from "components/PageLayout"
import CoursesList from "./CoursesList"

const Courses = () => {
  const location = useLocation()
  const fromDashboard = location.state?.fromDashboard

  return (
    <>
      <div>Courses</div>

      {fromDashboard ? (
        <Drawer>
          <CourseList />
        </Drawer>
      ) : (
        <PageLayout>
          <CoursesList />
        </PageLayout>
      )}
    </>
  )
}

Always make sure your component never relies on the props coming from Link. Like in this example, if fromDashboard doesn’t exist it will render the <CoursesList>. You have to have some fallback/default value and you should check if state is existing, if(location.state) or location.state? like in the example above. If you don’t do that, your app might crash when you go directly to that link without clicking on <Link>.

You would not be able to do: const { fromDashboard } = location.state That would throw an error when not defined.

Passing props without state

Let’s take a look again at the comparison between state vs. not state:


with state :

<Link to={{pathname:"/", state:{fromDashboard: true }}}>

vs. without state :

<span style="color:#F76E10"><Link to={{pathname:"/", someData:{fromDashboard: true }}}></span>

The drawback of sharing props without state is that it doesn’t persist the state. That means that when you hit the back button or when you reload the page, you’ll not have { fromDashboard : true } in state.

When you pass the props through state, react router persists to the history.state which is using the history API that can be accessed in browsers through the DOM window object.

The only drawback of history.state object is that it’s limited to 640k characters when it’s been serialised. Just make sure you don’t exceed that.

So in most cases you would choose to share props through state. You could use this, in the exceptional case that you don’t care if the state is not there upon reload or back navigation.

If you’re interested in knowing more about that history API, you can log it in the browser’s console and play around with it. Open up Chrome, right click on the page, go to Inspect element and open up the console. Then type window.history or just history and click on ↵ Return on your keyboard. You should see something like this

console

Most probably state is null, history.length is the number of pages in the session history. Go ahead and fiddle with this, you could get some inspirations from the docs at Mozilla.

If you have a Gmail account, you can even see that they’re persisting to the history.state.

gmail

A use case of passing props with <Link>

Let’s say you have a form stepper. You’re on step 1, you have to type in your nationality. Based on your nationality, next step would be different.

So you click on this

<Link to={{ path: "/step2", state: { nationality: "Icelandic" } }}>
  Next step
</Link>

you get transferred to /step2 and in the second step you get access to your object, location.state.nationality. For countries that don’t belong to the European Union, I will show two input fields, for other countries I want to display only one input field.

Be careful, because if the user goes directly to somewebsite.com/step2 your state will disappear so the user should go back to the beginning of the form if you didn’t save the nationality on the server or persist it in localStorage.

Passing server data through <Link>

You could pass server data through Link in order to avoid requests to the server. Imagine that you have a page listing books. It lists the book title, author and page number. You just discovered that there are 322 pages in Ready Player One but not 321 pages as it says on your page.

books

So you click edit and you’re taken to somepage.com/book-modification/ready-player-one

readyplayerone

On the book modification page there is exactly the same data that you have already fetched, book title, author and page number. To get access to that already fetched data, you could lift up the state in order to reuse that data in /book-modification. But oh, imagine if the Link wrapping that pen edit button is perhaps deep down in the tree. Maybe it’s inside <PageLayout> —> <BookList> —> <Book> —> <BookInfo> —><BookActions> and you’d be required to do lots of props drilling to lift up the state. Maybe you don’t have any application management system in place and if you don’t need this information globally, context would be a overkill. It’s possible that in this case, passing props through could be a convenient solution.

Just be aware of the following points:

  • The url is not bookmarkable. You should handle the case when you go directly to the link from the browser, and in that case, fetch data to the server.
  • Imagine if somebody opens up your website in a tab in the morning then he will modify one of the books at 5pm. Instead of fetching fresh data to the server, you would be using the same data since this morning. So you won’t see if the page number has changed, unless you refresh the page. If fresh data is not a requirement, you can sans souci pass server data through Link.
  • Often before fetching data we don’t know the size of it. Be careful that the potential size of the server data won’t exceed the storage limit. You could have some backup plan and in that case, fetch the data again to the server.

Summary

So now we’ve learned …

  • In which cases passing props through Link can be practical..
    • to conditionally change component behaviour based on navigation history
    • to avoid lifting state or using context/app state management
    • to minimize the number of server requests
  • That you should be aware of …
    • that you won’t have the props if you go directly to the link so you should have some fallback / default functionality.
    • be careful not to exceed the storage limit of history’s state object
    • maybe link’s state is not the way to go if you want fetching fresh, up to date data

I hope this article has been helpful. It might be a little different between versions and maybe you have a class component but not a functional component. Let me know on Twitter if you have some questions or difficulties ! I’d be happy to answer you 🙂


Get more tips in my almost monthly newsletter about CSS & React!