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?
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>
);
};
I have this code:
import "./styles.css";
import "mvp.css";
import { useState } from "react";
import axios from "axios";
function Books() {
const [book, setBook] = useState("");
const [result, setResult] = useState([]);
const [apiKey, setApiKey] = useState(
""
);
function handleChange(event) {
const book = event.target.value;
setBook(book);
}
function handleSubmit(event) {
event.preventDefault();
axios
.get(
"https://www.googleapis.com/books/v1/volumes?q=" +
book +
"&key=" +
apiKey +
"&maxResults=20"
)
.then((data) => {
setResult(data.data.items);
})
.catch((error) => {
if (error.response) {
alert("No results found.");
} else if (error.request) {
alert("No results found.");
} else if (error.message) {
alert("No results found.");
}
});
}
return (
<div className="App">
<h1>Search For A Book</h1>
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
type="text"
onChange={handleChange}
className="input"
placeholder="Search..."
/>
<button type="submit">Go!</button>
</div>
</form>
{result.map((book) => (
<a target="_blank" href={book.volumeInfo.previewLink}>
<img src={book.volumeInfo.imageLinks.thumbnail} alt={book.title} />
</a>
))}
</div>
);
}
export default function App() {
return <Books />;
}
And I am trying to do some error handling, and I get my alert messages as expected, however after I click off the alert box I get redirected to a typeerror that says:
book.volumeInfo.imageLinks is undefined
I am trying to suppress this and just go back to the default screen, but I am not sure how to accomplish this.
I am trying to make a simple react app that pulls info from a MySQL database ("username", "balance", "purchases").
So far, I've used node and react to pull from the database with an HTTP query and to display each element on the website.
I then created the API query for searching the database for all entries that start with what I've typed into the search bar.
The issue I'm running into is how do I change the state of the elements that display the username, etc with the new filtered information from the API query? The search bar and data elements are two separate components so I can't use the use effect hook natively.
I cant use the filter method because the database is huge and I've sent my query limit to 100.
Here's my code so far:
PlayerData.js
import axios from 'axios';
import React,{useState, useEffect} from 'react';
const Player = () => {
const [playerData,setPlayerData]=useState([])
useEffect(()=>{
axios.get("http://localhost:3001/api/get").then((res)=>{
console.log(res.data)
setPlayerData(res.data)
})
.catch(err=>{
console.log(err);
})
},[])
return (
<>
{playerData.map((data,id)=>{
return <div className="Player" key={id}>
<span className="Username"> { data.name } </span>
<span className="Crystals"> { data.balance } </span>
<span className="DateModi"> {Object.keys(JSON.parse(data.items)).length} </span>
</div>
})}
</>
)
};
export default Player;
SearchBar.js
import { useState } from "react";
import axios from 'axios'
const Search = () => {
const [searchTerm, setSearchTerm] = useState("")
axios.get(`http://localhost:3001/api/getSearchName/${searchTerm}`).then((res)=>{
console.log(res.data)
})
return (
<div className="Search">
<input className = "InputField" type="text" placeholder="Enter Username:" onChange={e => {setSearchTerm(e.target.value)}}/>
<span className="SearchButton" onClick={console.log(searchTerm)}>
Search
</span>
</div>
)
};
export default Search;
If I understood the question correctly, you need to set the state of PlayerData to a shared component(App), and pass it to the Player.js component. Then when searching it will be overwritten and update the information in the Player.js
function App() {
const [playerData, setPlayerData] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = () =>
axios
.get("http://localhost:3001/api/get")
.then((res) => {
setPlayerData(res.data);
})
.catch((err) => {
console.log(err);
});
const handleSearch = (text) => {
const clearText = text.trim();
if (!clearText.length) {
fetchData();
return;
}
axios
.get(`http://localhost:3001/api/getSearchName/${clearText}`)
.then((res) => {
setPlayerData(res.data);
});
};
return (
<div>
<div>
<Search handleSearch={handleSearch} />
</div>
<div>
<Player playerData={playerData} />
</div>
</div>
);
}
Search.js
const Search = ({ handleSearch }) => {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="Search">
<input
className="InputField"
type="text"
placeholder="Enter Username:"
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<span className="SearchButton" onClick={() => handleSearch(searchTerm)}>
Search
</span>
</div>
);
};
Player.js
const Player = ({ playerData }) => {
return (
<>
{playerData?.length ? (
playerData.map((data, id) => {
return (
<div className="Player" key={id}>
<span className="Username"> {data.name} </span>
<span className="Crystals"> {data.balance} </span>
<span className="DateModi">
{" "}
{Object.keys(JSON.parse(data.items)).length}{" "}
</span>
</div>
);
})
) : (
<div>Loading...</div>
)}
</>
);
};
I started my first project in React.js and I'm following tutorial from youtube to learn some basics. I was fixing my problems right away but this time I can't do anything with it since yesterday.
I'm showing the code below, what's the issue? Why that error is showing up? Thank you.
This is App.js code:
import React, { useState, useRef, useEffect } from "react";
import Tasklist from "./Tasklist";
import { v4 as uuidv4 } from 'uuid';
const LOCAL_STORAGE_KEY = 'taskApp.tasks'
function App() {
const [tasks, setTasks] = useState ([])
const taskNameRef = useRef()
useEffect(() => {
const storedTasks = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (storedTasks) setTasks(storedTasks)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify (tasks))
}, [tasks])
function toggleTask(id) {
const newTasks = [...tasks]
const task = newTasks.find(task => task.id === id)
task.complete = !task.complete
setTasks()(newTasks)
}
function handleAddTask(e){
const name = taskNameRef.current.value
if (name === '') return
setTasks(prevTasks => {
return [...prevTasks, {id: uuidv4, name: name, complete: false}]
})
taskNameRef.current.value = null
}
return (
<>
<Tasklist tasks={tasks} toggleTask={toggleTask} />
<input ref={taskNameRef} type="text"/>
<button onClick={handleAddTask}>Add task</button>
<button>Clear completed</button>
<div>0 tasks left.</div>
</>
)
}
export default App;
This is Task.js code, where the issue begin:
import React from 'react'
export default function Task({ task, toggleTask }) {
function handelTaskClick() {
toggleTask(task.id)
}
return (
<div>
<label>
<input type='checkbox' checked={task.complete} onChange={handelTaskClick} ></input>
{task.name}
</label>
</div>
)
}
Tasklist.js code:
import React from 'react'
import Task from './Task'
export default function Tasklist({ tasks, toggleTasks }) {
return (
tasks.map(task => {
return <Task key={task.id} toggleTasks={toggleTasks} task={task} />
})
)
}
toggleTask is passed to TaskList, but TaskLists doesn't destructure it, but instead destructures toggleTasks prop, with an "s". TaskList then passes toggleTasks through to Task where it (correctly) attempts to destructure toggleTask.
Consistently name the passed toggleTask prop.
<Tasklist tasks={tasks} toggleTask={toggleTask} />
...
function Tasklist ({ tasks, toggleTask }) {
return (
tasks.map(task => {
return <Task key={task.id} toggleTask={toggleTask} task={task} />
})
)
}
...
function Task ({ task, toggleTask }) {
function handelTaskClick() {
toggleTask(task.id);
}
return (
<div>
<label>
<input type='checkbox' checked={task.complete} onChange={handelTaskClick} />
{task.name}
</label>
</div>
)
}
I have done a fetch from an API and I want to add a search component so I can be able to search a single user from the data fetched. And I need some help to be able to do it properly. Thanks for your help and I hope that I have been understandable. Here below you can see what I have done:
DataFethed.js:
import React, { useState, useEffect } from "react";
import axios from "axios";
import Search from "./Search";
function DataFethed() {
const [searchValue, setSearchValue] = useState("");
const [users, setUsers] = useState([]);
useEffect(() => {
axios
.get(
"https://gist.githubusercontent.com/benna100/5fd674171ea528d7cd1d504e9bb0ca6f/raw"
)
.then(res => {
console.log(res);
setUsers(res.data);
})
.catch(error => {
console.log(error);
});
}, []);
const searchHandler = value => {
setSearchValue(value);
};
let updateUsers = users.filter(item => {
return item.name.toLowerCase().includes(searchValue);
}, []);
return (
<div className="App">
<h5>Shift OverViewShift</h5>
<Search searchHandler={searchHandler} />
{updateUsers.map((shifts, index) => (
<DataFethed key={index} props={shifts} />
))}
<ul>
{users.map(user => (
<ol key={user.name}>
<br />
{user.name} <br />
{user.start} <br />
{user.end}
</ol>
))}
</ul>
</div>
);
}
export default DataFetched;
Search.js:
import React from "react";
const Search = ({ searchHandler }) => {
const handleSearchInputChange = e => {
searchHandler(e.target.value);
};
return (
<form className="search">
<input
onChange={handleSearchInputChange}
type="text"
placeholder="Search Name..."
/>
<i className="fas fa-search" type="submit" value="SEARCH"></i>
</form>
);
};
export default Search;
Due to the comments we exchanged, you just need a conditional rendering. Inside DataFetched, you should write this return statement:
return (
<div className="App">
<h5>Shift OverViewShift</h5>
<Search searchHandler={searchHandler} />
<ul>
{(searchValue === '' ? users : updateUsers).map(user => (
<ol key={user.name}>
<br />
{user.name} <br />
{user.start} <br />
{user.end}
</ol>
))}
</ul>
</div>
);
Basically, now, when searchValue is NOT empty (meaning you are searching), you use updateUsers to render the user. Instead, when searchValue is empty, you render ALL the user by using users.
Moreover, since you have the same structure in both users and updateUsers, you can use the same map() function: notice that the ternary operator only specifies if using users or updateUsers.