4
votes

React Router: Link and Route not passing state to Component

I’m building an online shopping app with React, Redux, and React Router. On the home page, all items are displayed. I want users to be able to click on an image and be taken to a page with item details for that item.

Here's how I’m trying to do that:

  • In App.js, I’m using BrowserRouter, Route, and Switch for routing
  • In Home.js, I’m using react-router's Link to link an image on the Home page to an item details page; the Link is supposed to pass the ID to the Item component as state (see the Home.js code below)
  • Item.js is supposed to render an items detail page. The ID of the item will be passed to the Item component, which will render the details for that item.

This seems simple, and I’ve seen a lot of discussion about this on SO and across the web. Here are some that I have already tried:

Display item details after clicking Link in React Router

React router how to click thru to detail components

Passing values through React-Router v4 <Link />

Tyler McGinnis - Pass props to React Router's Link component

Medium - How to pass props to React routes components

I’m able to get the URL to change to correspond to the item’s ID, but I can’t get the Item component to render with the right information, and sometimes I get errors depending on the method I’m using. I hard coded the words "Item Component" inside a div in Item.js, and that will render when I click on the images from the Home page. Otherwise, nothing will render or I receive the two following TypeErrors:

App.js:

<Route path="/products" render={(props) => (<Item {...props} id={this.props.location.id} />) }/>

export default withRouter(App);

<App /> is wrapped in <BrowserRouter> in index.js.

I receive the following error when clicking on an image from the home page:

TypeError: Cannot read property 'props' of undefined

When the above “render” code is removed from and replaced with “component={ Item }”, I receive this error from Item.js: TypeError: Cannot read property 'location' of undefined


Here are my other code snippets:

Item.js:

class Item extends React.Component {
    constructor() {
        super();

        this.state = {
            item: this.props.location.state
        }
    }

    render() {
        let item = this.state.item;
        return (
            <div className="item" key={item.id}>
                <div className="item-image">
                    <img src={item.img} alt={item.title} />
                </div>
                <div className="card-component">
                    <span className="item-title">{item.title}</span>
                    <p className="item-price"><b>${item.price}</b></p>
                    <p className="item-desc">{item.desc}</p>
                </div>
            </div>
        );
    }
}

Home.js (where all items are displayed), the item details page is linked like so:

<Link to = 
    {{ pathname: `/products/${item.category}`,
    search: `?id=${item.id}`,
    state: {id: `${item.id}`} }}
    component={ Item }>
    <img src={item.img} alt={item.title} />
</Link>

UPDATE

I have wrapped the <Item/> component in withRouter(). In Item.js, if I set the state in the constructor as { item: this.props.location.state }, nothing renders as it should (only a dollar sign is on the screen, since the price section contains a hard coded dollar sign).

I tried setting the Item.js state to null in the constructor, then calling this.setState with { item: this.props.location.state } in componentDidMount(). When I do this, I receive the following error:

TypeError: Cannot read property 'img' of null

Also, if I leave the render={(props) => (<Item {...props} id={this.props.location.id} />) } in the <Route> in App.js, I receive this error:

TypeError: Cannot read property 'props' of undefined

If I change <Route> back to component={ Item }, and the Item.js constructor state is set to { item: this.props.location.state }, just the dollar sign renders.

UPDATE: include App.js and Routes.js code

App.js

import React from 'react';
import {
  BrowserRouter as Router,
  withRouter,
  Switch
} from 'react-router-dom';
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/footer/Footer';
import { Routes } from './constants/Routes';


class App extends React.Component {
  render() {
    return (
      <Router>
        <div className="App">
          <Navbar />
          <Switch>
            { Routes }
          </Switch>
          <Footer />
        </div>
      </Router>
    );
  }
}

export default withRouter(App);

Routes.js

import React from 'react';
import { Route } from 'react-router-dom';
import Home from '../components/Home';
import Item from '../components/shopping/Item';
import Cart from '../components/cart/Cart';
import OrderStatus from '../components/footer/OrderStatus';
import GiftCards from '../components/footer/GiftCards';
import ReturnsExchanges from '../components/footer/Returns-Exchanges';
import Contact from '../components/footer/Contact';

export const Routes = <div>
            <Route exact path="/" component={ Home } />
            <Route path="/products" component={ Item }/>
            <Route path="/cart" component={ Cart } />
            <Route path="/order-status" component={ OrderStatus } />
            <Route path="/gift-cards" component={ GiftCards } />
            <Route path="/returns-exchanges" component={ ReturnsExchanges } />
            <Route path="/contact" component={ Contact } />
            </div>
1
Hi, Have you wrapped your App component with withRouter?vipulp
Hi, thank you for the comment. I just tried that, but I am now getting a new error. I will update my original post.Jenna
The new error seems to be related to BrowserRouter. Checkout my answer below. Hope it helps. Also, will be helpful to see your App.js code.vipulp
I'm sorry, I just saw your edit about the App.js code. I will edit my question and include it at the bottom.Jenna
I found this to be a very well-written question, I wish everyone would be as detailed as you when posting @JennaFiltenborg

1 Answers

4
votes

One way to access history objects properties in any component, you need to wrap that component with withRouter. import withRouter from react-router-dom and wrap your App component like this:

export default withRouter(App);

More on withRouter.

Also, in index.js, make sure your <App /> is wrapped inside <BrowserRouter>.

import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
   <BrowserRouter>
     <App />
   </BrowserRouter>
, document.getElementById('root'));

EDIT:

TypeError: Cannot read property 'props' of undefined

Replace your constructor with the following code. When implementing a constructor for React Component super(props) should be before any other statement otherwise props will be undefined in constructor.

constructor(props){
    super(props);
    this.state = {
        item: this.props.location.state
    }
}

Error: TypeError: Cannot read property 'img' of null

Reason: Initial value of the state should not be null. You may pass empty object {} but not null, in this case. You're trying to access img, title, price and desc in Item.js but only passing id in state of <Link> in Home.js.

<Link to = 
    {{ pathname: `/products/${item.category}`,
       search: `?id=${item.id}`,
       state: {id: `${item.id}`} //pass img, title, desc, price etc as well
    }}
    component={ Item }>
    <img src={item.img} alt={item.title} />
</Link>