Having trouble routing to a page that will update a data entry by making get request using data object's ID

Viewed 34

I'm trying to route a page to my project that will allow the user to edit an existing data entry within a MongoDB database, however I am getting this error. The intended function of the program is that a user will be able to select an edit button on a specific data entry, which will open a form allowing them to altar that entry's content. It should do this by making a get request using the data's ID, however there is something wrong happening when I try to concatenate the route string, and the data's ID. Here is my Main.js file, my VehiclesList.js file, and my EditVehicles.js file:

Main.js

    import React, { Component } from "react";
import "./index.css";
import { Routes, Route } from "react-router-dom";

import VehicleList from "../VehicleList/index";
import AddVehicle from "../addVehicle/index";
import EditVehicle from "../editVehicle";

class Main extends Component {
  render() {
    return (
      <div className="main">
        <Routes>
          <Route exact path="/" element={<VehicleList />} />
          <Route path="/list" element={<VehicleList />} />
          <Route path="/addVehicle" element={<AddVehicle />} />
          <Route path="/editVehicle/:id" element={<EditVehicle />} />
        </Routes>
      </div>
    );
  }
}

export default Main;

VehiclesList.js

import React, { Component, useState } from "react";
import "./index.css";
import axios from "axios";
import { Link } from "react-router-dom";
import VehicleService from "../services/services";

class VehicleList extends Component {
  constructor(props) {
    super(props);
    this.VehicleService = new VehicleService();
    this.state = {
      vehicles: [],
    };
    this.deleteVehicle = this.deleteVehicle.bind(this);
  }

  componentDidMount = () => {
    this.getVehicleList();
  };

  //get all vehicles
  getVehicleList() {
    axios
      .get("http://localhost:5000/vehicles")
      .then((res) => {
        console.log(res);
        this.setState({
          vehicles: res.data,
        });
      })
      .catch((err) => {
        console.log(err);
      });
  }

  //delete vehicle
  deleteVehicle(vehicleId) {
    this.VehicleService.deleteVehicle(vehicleId);
    this.getVehicleList();
    window.location.reload(false);
  }

  render() {
    const { vehicles } = this.state;
    return (
      <div className="vehicleList">
        <table className="vehicleList_table">
          <thead>
            <tr className="vehicleList_table_head">
              <th className="vehicleList_table_head_title">#</th>
              <th className="vehicleList_table_head_title">Make</th>
              <th className="vehicleList_table_head_title">Model</th>
              <th className="vehicleList_table_head_title">Year</th>
              <th className="vehicleList_table_head_title">Price</th>
              <th className="vehicleList_table_head_title">Status</th>
              <th className="vehicleList_table_head_title"></th>
              <th className="vehicleList_table_head_title"></th>
            </tr>
          </thead>
          <tbody className="vehicleList_table_body">
            {vehicles &&
              vehicles.map((vehicle, idx) => {
                let status = "";
                if (vehicle.isSold) {
                  status = "Sold";
                } else {
                  status = "Available";
                }
                return (
                  <tr key={idx}>
                    <td className="vehicleList_table_item">{vehicle._id}</td>
                    <td className="vehicleList_table_item">{vehicle.make}</td>
                    <td className="vehicleList_table_item">{vehicle.model}</td>
                    <td className="vehicleList_table_item">{vehicle.year}</td>
                    <td className="vehicleList_table_item">{vehicle.price}</td>
                    <td className="vehicleList_table_item">{status}</td>
                    <td className="vehicleList_table_item">
                      <Link
                        to={"/editVehicle/" + vehicle._id}
                        style={{ fontSize: "1.5rem" }}
                        className="edit_btn"
                      >
                        edit
                      </Link>
                    </td>
                    <td className="vehicleList_table_item">
                      <button
                        onClick={() => this.deleteVehicle(vehicle._id)}
                        className="delete_btn"
                      >
                        Delete
                      </button>
                    </td>
                  </tr>
                );
              })}
          </tbody>
        </table>
      </div>
    );
  }
}

export default VehicleList;

EditVehicles.js

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

class EditVehicle extends Component {
  constructor(props) {
    super(props);
    this.state = {
      make: "",
      model: "",
      year: "",
      price: "",
      isSold: "",
    };
  }

  componentDidMount = () => {
    this.getVehicleById();
  };

  //get vehicle by ID
  getVehicleById() {
    axios
      .get(
        "http://localhost:5000/vehicles/editVehicle/" +
          this.props.match.params.id
      )
      .then((res) => {
        this.setState({
          make: res.data.make,
          model: res.data.model,
          year: res.data.year,
          price: res.date.price,
          isSold: res.data.isSold,
        });
      })
      .catch((err) => {
        console.log(err);
      });
  }

  //handle changes to data
  changeHandler = (e) => {
    this.setState({ [e.target.name]: e.target.value });
  };

  //update record on submit
  submitHandler = (e) => {
    e.preventDefault();
    const { make, model, year, price, isSold } = this.state;
    axios
      .post(
        "http://localhost:5000/vehicles/editVehicle/" +
          this.props.match.params.id,
        {
          make: make,
          model: model,
          year: year,
          price: price,
          isSold: isSold,
        }
      )
      .then((res) => {
        console.log(res);
        this.props.history.push("/");
      })
      .catch((err) => {
        console.log(err);
      });
  };

  render() {
    return (
      <div className="editVehicle">
        <form className="editVehicle_form" onSubmit={this.submitHandler}>
          <Link to="/" className="back_btn">
            Back
          </Link>

          <h1>Update Vehicle Details</h1>
          <p>Please fill out all fields</p>
          <input
            name="make"
            placeholder="Make"
            type="text"
            value={this.state.make}
            onChange={this.changeHandler}
            className="editVehicle_form_input"
          />
          <br />
          <input
            name="model"
            placeholder="Model"
            type="text"
            value={this.state.model}
            onChange={this.changeHandler}
            className="editVehicle_form_input"
          />
          <br />
          <input
            name="year"
            placeholder="Year"
            type="text"
            value={this.state.year}
            onChange={this.changeHandler}
            className="editVehicle_form_input"
          />
          <br />
          <input
            name="price"
            placeholder="Price"
            type="text"
            value={this.state.price}
            onChange={this.changeHandler}
            className="editVehicle_form_input"
          />
          <br />
          <label className="addVehicle_form_radio_label">
            Available
            <input
              name="isSold"
              type="radio"
              value={false}
              onChange={this.changeHandler}
              className="editVehicle_form_radio"
            />
          </label>
          <label className="editVehicle_form_radio_label">
            Sold
            <input
              name="isSold"
              type="radio"
              value={true}
              onChange={this.changeHandler}
              className="editVehicle_form_radio"
            />
          </label>
          <br />
          <input type="submit" value="Save" className="editVehicle_submit" />
        </form>
      </div>
    );
  }
}

export default EditVehicle;

My Routes to the VehiclesList, AddVehicle and DeleteVehicle pages all work. the only issue I'm running into is routing the EditVehicle page.

at first I was getting the error "Cannot read properties of undefined('params'), and then I changed the route to the EditVehicles page to this:

<Route path="/editVehicle/:id" component={EditVehicle} />

when I did it this way, I got an error back saying that this route did not have an element, and would result in an empty page.

I also tried doing it this way:

<Route path='/editVehicle/:id render={(props) => <EditVehicle {...props} />} />

this too just gave me an error saying that the route did not have an element.

is there an issue in my code that I am not seeing? or is there an easier way to do this?

2 Answers

The error tells you, that you don't have element attribute in Route:

<Route path="/editVehicle/:id" component={EditVehicle} />

Replace component to element

<Route path="/editVehicle/:id" element={<EditVehicle/>} />

You messed up with different versions of React-router-dom implementations. see docs

Try to add useParams hook from React-router-dom in your EditVehicle component to get id:

let { id } = useParams();

Issue

The "edit vehicle" route is correct:

<Route path="/editVehicle/:id" element={<EditVehicle />} />

The issue is that in react-router-dom@6 there are no longer any route props. In other words, EditVehicle is a class-based component and this.props.match and this.props.history are undefined. It's only when the code attempts to access into this undefined object (i.e. this.props.match.params) that the error is thrown.

Additionally, the history object is no longer directly exposed in RRDv6, you'll need to use the useNavigate hook to access a navigate function.

Solution

You can either convert EditVehicle to a React function component or create a new custom withRouter Higher Order Component so that the route path params can be injected as props like the class component is expecting.

  • Using custom withRouter HOC

    Create a withRouter HOC that can use the React hooks and inject new props for the class component to access.

     import { useNavigate, useParams, /* other hooks */ } from 'react-router-dom';
    
     const withRouter = Component => props => {
       const navigate = useNavigate();
       const params = useParams();
       // ... other hooks ...
       return (
         <Component
           {...props}
           {...{ navigate, params, /* other props from hooks */ }}
         />
       );
     };
    

    Decorate the EditVehicle component export:

     import withRouter from '../path/to/withRouter';
    
     ...
    
     export default withRouter(EditVehicle);
    

    params and navigate (and others) are now injected as props, access accordingly in EditVehicle:

     // get vehicle by ID
     getVehicleById() {
       const { params: { id } } = this.props;
    
       axios.get("http://localhost:5000/vehicles/editVehicle/" + id)
         .then((res) => {
           const { data: { make, model, year, price, isSold } } = res;
           this.setState({ make, model, year, price, isSold });
         })
         .catch(console.log);
     }
    
     // update record on submit
     submitHandler = (e) => {
       e.preventDefault();
       const { make, model, year, price, isSold } = this.state;
       const { navigate, params: { id } } = this.props;
    
       axios
         .post(
           "http://localhost:5000/vehicles/editVehicle/" + id,
           { make, model, year, price, isSold }
         )
         .then((res) => {
           console.log(res);
           navigate("/");
         })
         .catch(console.log);
     };
    
  • Conversion to React function component

    • Use the useNavigate and useParams hooks directly
    • Use React.useState to store the form field values
    • use React.useEffect to update the state when the id path param updates
    • Convert all class methods to functions, and remove references to this

    Code:

     import React, { useEffect, useState } from "react";
     import axios from "axios";
     import { Link, useParams, useNavigate } from "react-router-dom";
    
     const EditVehicle = () => {
       const navigate = useNavigate();
       const { id } = useParams();
    
       const [state, setState] = useState({
         make: "",
         model: "",
         year: "",
         price: "",
         isSold: ""
       });
    
       useEffect(() => {
         // get vehicle by ID
         const getVehicleById = (id) => {
           axios
             .get("http://localhost:5000/vehicles/editVehicle/" + id)
             .then((res) => {
               const { make, model, year, price, isSold } = res.data;
               setState({ make, model, year, price, isSold });
             })
             .catch(console.log);
         };
    
         getVehicleById(id);
       }, [id]);
    
       //handle changes to data
       const changeHandler = (e) => {
         const { name, value } = e.target;
         setState((state) => ({
           ...state,
           [name]: value
         }));
       };
    
       //update record on submit
       const submitHandler = (e) => {
         e.preventDefault();
         const { make, model, year, price, isSold } = state;
         axios
           .post("http://localhost:5000/vehicles/editVehicle/" + id, {
             make,
             model,
             year,
             price,
             isSold
           })
           .then((res) => {
             console.log(res);
             navigate("/");
           })
           .catch(console.log);
       };
    
       return (
         <div className="editVehicle">
           <form className="editVehicle_form" onSubmit={submitHandler}>
             <Link to="/" className="back_btn">
               Back
             </Link>
    
             <h1>Update Vehicle Details</h1>
             <p>Please fill out all fields</p>
             <input
               name="make"
               placeholder="Make"
               type="text"
               value={state.make}
               onChange={changeHandler}
               className="editVehicle_form_input"
             />
             <br />
             <input
               name="model"
               placeholder="Model"
               type="text"
               value={state.model}
               onChange={changeHandler}
               className="editVehicle_form_input"
             />
             <br />
             <input
               name="year"
               placeholder="Year"
               type="text"
               value={state.year}
               onChange={changeHandler}
               className="editVehicle_form_input"
             />
             <br />
             <input
               name="price"
               placeholder="Price"
               type="text"
               value={state.price}
               onChange={changeHandler}
               className="editVehicle_form_input"
             />
             <br />
             <label className="addVehicle_form_radio_label">
               Available
               <input
                 name="isSold"
                 type="radio"
                 value={false}
                 onChange={changeHandler}
                 className="editVehicle_form_radio"
               />
             </label>
             <label className="editVehicle_form_radio_label">
               Sold
               <input
                 name="isSold"
                 type="radio"
                 value={true}
                 onChange={changeHandler}
                 className="editVehicle_form_radio"
               />
             </label>
             <br />
             <input type="submit" value="Save" className="editVehicle_submit" />
           </form>
         </div>
       );
     };
    
Related