3

I am new to React overall and very new to React Router. I am building an app in which a list of reviews is displayed and when a user clicks a review card, they are redirected to a page that displays that card's details. I have the majority of the logic coded, but I am struggling with implementing React router. Here are my components so far.

import React from 'react';
import  ReviewCollection  from './components/ReviewCollection';
import  ReviewCard  from './components/ReviewCard';
import { Route, Router, browserHistory } from 'react-router';

class App extends React. Component {

  render() {
    return (
      <Router >
         <Router history={browserHistory} />
         <ReviewCollection />
          <Route path={"/details"} component={ReviewCard} />
      </Router>
    );
  }
}

export default App;
import React from 'react';
import ReviewCard from './ReviewCard';
import { reviews } from '../data';
import {reactLocalStorage} from 'reactjs-localstorage';
import { browserHistory } from 'react-router';

class ReviewCollection extends React.Component {

    goToDetails = (review) => {
        reactLocalStorage.set('selectedReview', review);
       browserHistory.push('/details');
      };

      render() {
          return (
                <div className='card-collection'>
                    {reviews
                    .filter((review, idx) => idx < 24)
                    .map((review) => (
                     <div onClick={() => this.goToDetails(review)}>
                     <ReviewCard key={review.id} review={review} />
                      </div>
                    ))}
            </div>
          )
      }
}

export default ReviewCollection;
import React from 'react';
import { reviews } from '../data';
import StarRatings from 'react-star-ratings';
import './Review.css';  

 const ReviewCard= ({ review }) => {
  return (  
    <div class="card-deck">
      {reviews.map((review) => {
        return (
         <div class="card">
          <div key={review.id}>
            <h4 class="card-title">{review.place}</h4>
            <StarRatings
                rating={review.rating}
                starRatedColor="gold" 
                starDimension="20px"
                />
            <div class="card-body">{review.content}</div>
            <div class="card-footer">{review.author} - {review.published_at}</div> 
            
      </div>
      </div>
        );
      })}
      </div> 
          );
      };
  
export default ReviewCard;

I am not confident as to whether or not I am headed in the right direction with how my routes are written overall, and in the browser, I am getting this error: "TypeError: Cannot read property 'getCurrentLocation' of undefined" Can somebody please help me identify the problem? Thanks

Edit #1 Thanks for the advice guys. Here is my App.js component now that I have started to implement react-router-dom instead of react router:

import React from 'react';
import  ReviewCollection  from './components/ReviewCollection';
import  ReviewCard  from './components/ReviewCard';
import { BrowserRouter, Router, Route } from 'react-router-dom'; 
import history from './history';


class App extends React.Component {
  render() {
    return (
      <BrowserRouter >
         <Router history={history} />
         <ReviewCollection />
          <Route path={"/details"} component={ReviewCard} />
      </BrowserRouter>
    );
  }
}

export default App;

But I am still confused as to how I should use browserHistory now in my ReviewCollection.js file. That has not changed and is still using the code browserHistory.push('/details');

1 Answers1

2

It's better to use react-router-dom instead of react-router.Becuase react-router-dom exports dom aware components most interestingly browser.history.

Check this for more info - react-router vs react-router-dom, when to use one or the other?

Since you're using browserHistory.push(), better use withRouter HOC to do a redirection to the relevant page you want.

Check this for more info on that - How to push to History in React Router v4?

Based on OP's suggestions, I believe it's essential to have 2 different paths for home page of the reviews and details page for a particular review (once user clicks). Therefore, using BrowserRouter, it is possible to create routings for both pages. Home page contains ReviewCollection which should render array of ReviewHeader components. In review page, for a particular review, it should render ReviewDetails but not the ReviewHeader which should be kept separated, otherwise it will create an issue of using same component in 2 different places as you've encountered.

As I told earilier, in order to do a redirection, use withRouter HOC instead of BrowserHistory. Since it's an HOC, you need to wrap the component to use the history object extracted as a prop.

Use the following code-snippets as supprotive material for configuring BrowserRouter and withRouter HOC.

And check this to get more info on react-router: https://reactrouter.com/web/guides/quick-start

App.js

import React from "react";
import ReviewCollection from "./ReviewCollection";
import ReviewCardDetails from "./ReviewCardDetails";
import { BrowserRouter as Router, Route } from "react-router-dom";

class App extends React.Component {
  render() {
    return (
      <Router>
        <Route exact path="/" component={ReviewCollection} />
        <Route exact path="/details" component={ReviewCardDetails} />
      </Router>
    );
  }
}

export default App;

In App module, I've given exact as a flag to Route component in order to keep our custom component mounting exactly for the given path. Otherwise ReviewCollection component will be kept intact even in /details route.

ReviewCollection

import React from "react";
import ReviewCardHeader from "./ReviewCardHeader";
import { reactLocalStorage } from "reactjs-localstorage";
import { withRouter } from "react-router-dom";

const reviews = [
  {
    id: 1,
    place: "title 1",
    rating: 10,
    content: "content 1",
    author: "author 1",
    published_at: "published_at 1",
  },
  {
    id: 2,
    place: "title 2",
    rating: 12,
    content: "content 2",
    author: "author 2",
    published_at: "published_at 2",
  },
];
class ReviewCollection extends React.Component {
  goToDetails = (review) => {
    reactLocalStorage.set("selectedReview", review);
    this.props.history.push({ pathname: "/details", state: { review } });
  };

  render() {
    return (
      <div className="card-collection">
        {reviews
          .filter((review, idx) => idx < 24)
          .map((review) => (
            <div onClick={() => this.goToDetails(review)}>
              <ReviewCardHeader key={review.id} review={review} />
            </div>
          ))}
      </div>
    );
  }
}

export default withRouter(ReviewCollection);

ReviewCardHeader

import React from "react";
import StarRatings from "react-star-ratings";

const ReviewCardHeader = ({ review }) => {
  return (
    <div key={review.id} className="card-deck">
      <div className="card">
        <div>
          <h4 className="card-title">{review.place}</h4>
          <StarRatings
            rating={review.rating}
            starRatedColor="gold"
            starDimension="20px"
          />
          <div className="card-body">{review.content}</div>
          <div className="card-footer">
            {review.author} - {review.published_at}
          </div>
        </div>
      </div>
    </div>
  );
};

export default ReviewCardHeader;

ReviewCardDetails

import React from "react";
import { useLocation } from "react-router-dom";
import StarRatings from "react-star-ratings";

const ReviewCardDetails = () => {
  const location = useLocation();
  const { review } = location?.state; // ? - optional chaining

  console.log("history location details: ", location);
  return (
    <div key={review.id} className="card-deck">
      <div className="card">
        <div>
          <h4 className="card-title">{review.place}</h4>
          <StarRatings
            rating={review.rating}
            starRatedColor="gold"
            starDimension="20px"
          />
          <div className="card-body">{review.content}</div>
          <div className="card-footer">
            {review.author} - {review.published_at}
          </div>
        </div>
      </div>
    </div>
  );
};

export default ReviewCardDetails;

Home Page View

enter image description here

Details Page View

enter image description here

Hope this would be helpful to solve your issue. And make sure to have different details for header and details component since header means a summary of a particular review.

Kavindu Vindika
  • 2,449
  • 1
  • 13
  • 20
  • Hi there thanks for your help! If you would not mind checking my edit on my post, I am still a bit stuck. I read through the documentation you sent me and still do not quite understand how to use the browserHistory.push() here. thank you for your help! –  Aug 17 '21 at 21:25
  • Have a small issue, why do you have `reviews.map()` in **ReviewCard** component, which actually needs to show the view relevant to a particular review instead of using it for all the reviews. And also, since **review** object is passed from parent component to ReviewCard, it shows a lint error: `'review' is already declared in the upper scope`. Therefore, I believe it should also need to be corrected. – Kavindu Vindika Aug 17 '21 at 22:26
  • And also use `className` instead of using `class` for JSX elements – Kavindu Vindika Aug 17 '21 at 22:29
  • Oh wow, thank you so much for all of your help! I just saw your code, thank you!! I really appreciate this! –  Aug 17 '21 at 23:30
  • Use it as a supportive material to modify your implementation and please kindly accept the answer if it's perfectly appropriate as a solution for your issue by clicking on tick button to the left corner of the answer. – Kavindu Vindika Aug 17 '21 at 23:39