JorahFriendzone
JorahFriendzone

Reputation: 433

How to generate dynamic routes in react router?

I'm trying to create a list of houses and each house upon clicking will point to a separate page with data on that house. Currently all the data is static data I created in my constructor - named leaseData. I want the Route path to point to /dashboard/lease-id. I'm trying to pass both the address and the monthlyRent into the Lease component so that I can render that data in the new component. How do I do this?

import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import { Lease } from './Lease'

class Dashboard extends Component {
  constructor(props) {
    super(props)
    this.match = this.props.match

    this.leaseData = [
      {
        id: 1,
        address: '18291 Rainbow Dr, Cool Springs CA 93102',
        monthlyRent: 4300
      },
      {
        id: 2,
        address: '2200 Stiller Dr, Las Vegas NV 96274',
        monthlyRent: 1500
      },
      {
        id: 3,
        address: '21626 Sleepy Hollow Ct, Walnut CA 91763',
        monthlyRent: 2400
      }
    ]
  }

  render() {

    let linkList = this.leaseData.map(lease => {
      return (
        <li>
          <Link to={`${this.match.url}/${lease.id}`}>
          <img src="https://cdn.edinarealty.com/media/2256/homepage-tiles_new-construction.jpg" alt="HTML5 Icon"></img>
          </Link>
        </li>
      )
    })

    return (
      <div>
        MAIN DASHBOARD
        <ul>
         <li> {linkList} </li>
        </ul>
        <Route path={`${this.match.url}/:lease-id`} render={<Lease data={}/>}/>
      </div>
    )
  }
}

Upvotes: 0

Views: 1166

Answers (3)

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15688

Let's try to separate the App and Dashboard logic so we can render things more cleanly.

Here's a sandbox for reference: https://codesandbox.io/s/suspicious-merkle-4bxv3

App.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import Dashboard from "./Dashboard";
import Lease from "./Lease";

import "./styles.css";

class App extends React.Component {
  state = {
    leaseData: [
      {
        id: 1,
        address: "18291 Rainbow Dr, Cool Springs CA 93102",
        monthlyRent: 4300
      },
      {
        id: 2,
        address: "2200 Stiller Dr, Las Vegas NV 96274",
        monthlyRent: 1500
      },
      {
        id: 3,
        address: "21626 Sleepy Hollow Ct, Walnut CA 91763",
        monthlyRent: 2400
      }
    ],
    selected: null
  };

  changeSelected = id => {
    this.setState({
      selected: this.state.leaseData.find(lease => lease.id == id)
    });
  };

  render() {
    return (
      <BrowserRouter>
        <Route
          path="/"
          exact
          render={props => <Dashboard data={this.state.leaseData} />}
        />
        <Route
          path="/lease/:id"
          render={props => (
            <Lease
              {...props}
              selected={this.state.selected}
              changeSelected={this.changeSelected}
            />
          )}
        />
      </BrowserRouter>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Dashboard.js

import React, { Component } from "react";
import { Link } from "react-router-dom";

class Dashboard extends Component {
  render() {
    let linkList = this.props.data.map((lease, index) => {
      return (
        <li key={index}>
          <Link to={`/lease/${lease.id}`}>
            <img
              src="https://cdn.edinarealty.com/media/2256/homepage-tiles_new-construction.jpg"
              alt="HTML5 Icon"
            />
          </Link>
        </li>
      );
    });

    return (
      <div>
        MAIN DASHBOARD
        <ul>{linkList}</ul>
      </div>
    );
  }
}

export default Dashboard;

Lease.js

import React, { useEffect } from "react";
import { Link } from "react-router-dom";

const Lease = props => {
  useEffect(() => {
    const id = props.match.params.id;
    props.changeSelected(id);
  }, []);

  const { selected } = props;

  if (!selected) {
    return <div>Loading...</div>;
  } else {
    return (
      <div>
        <Link to="/">Back to home</Link>
        <h4>Id: {selected.id}</h4>
        <h4>Address: {selected.address}</h4>
        <h4>Rent: {selected.monthlyRent}</h4>
      </div>
    );
  }
};

export default Lease;

Main notes:

  1. App.js contains our router, lease-data and selected lease.
  2. By using the render prop we can pass in data from the App-state, to the component of the Route. For the Dashboard Route, we pass in the entire lease-data. For the Lease route, we pass in the changeSelected() to help retrieve the selected lease item, and selected which is that found item. Additionaly, we spread {...props} to get access to props like match and history.
  3. In Dashboard, we use the same list-generating logic you setup to display the items.
  4. When you click an image and get redirected to the Lease component, we fire the changeSelected function. This happens in useEffect(), we use props.match.params.id to get the id of the lease (also in the URL) we navigated to via Link.
  5. Pass that value to changeSelected() and it will update the selected value in the App-state with the desginated lease-object in our list.
  6. App.js gets re-rendered (due to state-change) and passes down the selected item state-value to Lease, which we will use to display the information on that lease object.

Overall it's a clean-way of doing this before integrating redux.

Upvotes: 1

Asaf Aviv
Asaf Aviv

Reputation: 11760

sandbox

Extract data from Dashboard this will let us the ability to look for it by id when Lease component mounts

You can replace it with an api call when you implement the backend

const data = [
  {
    id: 1,
    address: "18291 Rainbow Dr, Cool Springs CA 93102",
    monthlyRent: 4300
  },
  {
    id: 2,
    address: "2200 Stiller Dr, Las Vegas NV 96274",
    monthlyRent: 1500
  },
  {
    id: 3,
    address: "21626 Sleepy Hollow Ct, Walnut CA 91763",
    monthlyRent: 2400
  }
];

Extract the leaseId from match.params.leaseId and look it up when the component mounts using useEffect with leaseId as a dependency

When you have the api ready you can just replace data.find with an API call with the leaseId

The reason 'im looking it up on mount is that it will give us the ability to go straight to the URL without needing to go through the dashboard to load the data

const Lease = ({
  match: {
    params: { leaseId }
  }
}) => {
  const [leaseData, setLeaseData] = useState(null);

  useEffect(() => {
    setLeaseData(data.find(d => d.id.toString() === leaseId));
  }, [leaseId]);

  if (!leaseData) return null;

  return (
    <div>
      <h1>{leaseData.id}</h1>
      <p>{leaseData.address}</p>
      <p>{leaseData.monthlyRent}</p>
    </div>
  );
};

When the url is /dashboard use the render prop to render the main Dashboard and create a Route path to leaseId

const Dashboard = ({ match: { url } }) => {
  const linkList = data.map(lease => {
    return (
      <li key={lease.id}>
        <Link to={`${url}/${lease.id}`}>
          <img
            src="https://cdn.edinarealty.com/media/2256/homepage-tiles_new-construction.jpg"
            alt="HTML5 Icon"
          />
        </Link>
      </li>
    );
  });

  return (
    <Switch>
      <Route
        exact
        path={url}
        render={() => (
          <div>
            MAIN DASHBOARD
            <ul>{linkList}</ul>
          </div>
        )}
      />
      <Route path={`${url}/:leaseId`} component={Lease} />
    </Switch>
  );
};

const Home = () => (
  <div>
    <Link to="/dashboard">Dashboard</Link>
  </div>
);

const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/dashboard" component={Dashboard} />
    </Switch>
  </Router>
);

Upvotes: 0

adam_th
adam_th

Reputation: 407

You could use a router switch and generate your lease routes like so:

import React, { Component } from 'react'
import { Switch, Link, Route } from 'react-router-dom'
import { Lease } from './Lease'

class Dashboard extends Component {
  constructor(props) {
    super(props)
    this.match = this.props.match

    this.leaseData = [
      {
        id: 1,
        address: '18291 Rainbow Dr, Cool Springs CA 93102',
        monthlyRent: 4300
      },
      {
        id: 2,
        address: '2200 Stiller Dr, Las Vegas NV 96274',
        monthlyRent: 1500
      },
      {
        id: 3,
        address: '21626 Sleepy Hollow Ct, Walnut CA 91763',
        monthlyRent: 2400
      }
    ]
  }

  render() {

    let linkList = this.leaseData.map(lease => {
      return (
        <li>
          <Link to={`${this.match.url}/${lease.id}`}>
          <img src="https://cdn.edinarealty.com/media/2256/homepage-tiles_new-construction.jpg" alt="HTML5 Icon"></img>
          </Link>
        </li>
      )
    })

    let leaseRoutes = this.leaseData.map(lease => <Route exact path={`${this.match.url}/${lease.id}`} render={<Lease data={lease}/>}/>)

    return (
      <div>
        MAIN DASHBOARD
        <ul>
         <li> {linkList} </li>
        </ul>
        <Switch>
            {leaseRoutes}
        </Switch>
        <Route path={`${this.match.url}/:lease-id`} render={<Lease data={}/>}/>
      </div>
    )
  }
}

Upvotes: 0

Related Questions