Reputation: 15
I am working on an E-commerce website and adding a 'cart' route on it, but it got the following Error: Error image The localhost address is :3000/cart, but the error address is from :3000/api/products/cart.(I am using express to fetch data from :http://localhost:5000/api/products)
Also, if I input the address as:" http://localhost:3000/cart/223", the error came out as:
GET http://localhost:3000/cart/api/products/cart 404 (Not Found)
App.js :
function App() {
return (
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/products" component={ProductPage}/ >
<Route path="/:id" component={ProductDetail} />
<Route path="/cart" component={CartPage} />
<Route path="/cart/:id" component={CartPage} />
</Switch>
)
}
export default App;
cartPage.jsx :
import React, { useEffect } from "react";
import { addToCart } from "../actions/cartActions";
import { useDispatch } from "react-redux";
function CartPage(props) {
const productId = props.match.params.id;
const qty = props.location.search? Number(props.location.search.split("=")[1]): 1;
const dispatch = useDispatch();
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, []);
return <div>Hell world!</div>;
}
export default CartPage;
productDetail.jsx:
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { detailsProduct } from "../actions/productActions";
function ProductDetail(props) {
const [qty, setQty] = useState(1);
const productDetails = useSelector((state) => state.productDetails);
const { product, loading, error } = productDetails;
const dispatch = useDispatch();
useEffect(() => {
dispatch(detailsProduct(props.match.params.id));
return () => {//};
}, []);
const handleAddToCart = () => {
props.history.push("/cart/" + props.match.params.id + "?qty=" + qty);
};
return ({product.countInStock > 0 && (
<button onClick={handleAddToCart}>
Add to Cart
</button>
)})
http://localhost:5000/api/products
:
[
{
name: "Slim Shirt",
category: "Shirts",
image: "/img/d2.png",
price: 60,
brand: "Nike",
rating: 4.5,
numReviews: 10,
_id: "123",
countInStock: 0,
},
{
name: "Best Shirt",
category: "Shirts",
image: "/img/d3.png",
price: 50,
brand: "ads",
rating: 4.6,
numReviews: 12,
_id: "223",
countInStock: 6,
},
....}
server.js:
import express from "express";
import { data } from "./data";
const app = express();
app.get("/api/products/:id", (req, res) => {
const product = data.find((x) => x._id === req.params.id);
if (product) {
res.send(product);
} else {
res.status(404).send({ msg: "Product Not Found!!" });
}
});
app.get("/api/products", (req, res) => {
res.send(data);
});
app.listen(5000, () => {
console.log("server started at http://localhost:5000");
});
package.jason:
{
"name": "frontend",
"proxy": "http://127.0.0.1:5000",
"version": "0.1.0",
"private": true,
"dependencies": {
.....}
//store.js:
import { createStore, combineReducers, compose,
applyMiddleware } from "redux";
import {
productDetailsReducer,
productListReducer,
} from "./reducers/productReducers";
import thunk from "redux-thunk";
import { cartReducer } from "./reducers/cartReducers";
const initialState = {};
const reduer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer,
cart: cartReducer,
});
const composeEnhancer =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reduer,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
//productActions.js:
const listProducts = () => async (dispatch) => {
try {
dispatch({ type: PRODUCT_LIST_REQUEST });
const { data } = await axios.get("api/products");
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data
});
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload:
error.message });
}
};
const detailsProduct = (productId) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload:
productId });
const { data } = await axios.get("api/products/" +
productId);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data
});
} catch (error) {
dispatch({ type: PRODUCT_DETAILS_FAIL, payload:
error.message });
}
};
export { listProducts, detailsProduct };
Upvotes: 1
Views: 600
Reputation: 203408
The issue seems to start with the order of the routes rendered by the Switch
component. Route path order and specificity matters within the Switch
. The "/:id"
route path is less specific than "/cart"
which is less specific than "/cart/:id"
, and so it will be matched and rendered by the Switch
.
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/products" component={ProductPage} />
<Route path="/:id" component={ProductDetail} /> // matches "/cart"
<Route path="/cart" component={CartPage} /> // unreachable route!!
<Route path="/cart/:id" component={CartPage} /> // unreachable route!!
</Switch>
The last two routes are unreachable. The wrong component is rendered when the path is "/cart"
and an invalid request is made.
ProductDetail dispatches an action that is likely meant to make a fetch request with a proper id, bu "cart"
is the id
value with path "/cart"
matched by the "/:id"
route.
useEffect(() => {
dispatch(detailsProduct(props.match.params.id));
return () => {};
}, []);
Reorder the routes in inverse order of path specificity. In most cases doing this also completely removes the need to pepper all the routes with the exact
prop.
Example:
<Switch>
<Route path="/cart/:id" component={CartPage} />
<Route path="/cart" component={CartPage} />
<Route path="/products" component={ProductPage} />
<Route path="/:id" component={ProductDetail} />
<Route path="/" component={HomePage} />
</Switch>
Note that "/cart/:id"
is more specific than "/cart"
, "/products"
, and "/:id"
, and that both "/cart"
and "/products"
are more specific than "/:id"
, and they are all more specific than the last route path "/"
.
Note also that react-router-dom@5
allows the Route
component to use an array of paths for the path
prop. Keep in mind that the order and specificity still matters within the array. Keep also in mind that all routes in the array should be more specific than the paths of routes below/after.
Example:
<Switch>
<Route path={["/cart/:id", "/cart"]} component={CartPage} />
<Route path="/products" component={ProductPage} />
<Route path="/:id" component={ProductDetail} />
<Route path="/" component={HomePage} />
</Switch>
For the requests try using an absolute path, i.e. axios.get("/api/products/" + productId
) so the request isn't being made from the "/products"
sub-route. You want the API request path to be something like "/api/products/123"
instead of "/products/api/products/123"
.
Example:
const listProducts = () => async (dispatch) => {
try {
dispatch({ type: PRODUCT_LIST_REQUEST });
const { data } = await axios.get("/api/products");
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
const detailsProduct = (productId) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
const { data } = await axios.get("/api/products/" + productId);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_DETAILS_FAIL, payload: error.message });
}
};
export { listProducts, detailsProduct };
Upvotes: 1