1

There is a routing problem with using react-router-dom v6 to maintain "cart items" functionality in my app. So it doesn't show cart items when I click on the "cart" link in the header navbar. In contrast, it works and displays the list when I try to add a new item to the cart.

Note: It shows the cart items when the URL path is such this pattern 'http://localhost:3000/cart/1?qty=2' and doesn't show in the case of 'http://localhost:3000/cart'!

Please follow the code...

App.js

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomeScreen from './screens/HomeScreen';
import CartScreen from './screens/CartScreen';
import ProductScreen from './screens/ProductScreen';
 
function App() {
  return (
    <>
      <Header/>
      <Routes>
        <Route path="/" element={<HomeScreen />} exact />
        <Route path="/product/:id" element={<ProductScreen />} />
        <Route path="/cart/:productid?" element={<CartScreen />}/>
      </Routes>
    );
  </>
}

I have configured all the routes, and if you focus on the cart path we add '/cart/' followed by ': productid?' as a product parameter

ProductScreen.js

import { useParams, Link, useNavigate } from 'react-router-dom'; 
import { useDispatch, useSelector } from 'react-redux';
import {Button} from 'react-bootstrap';

function ProductScreen() {
  const { id } = useParams(); // the product 'id'
  const navigate = useNavigate();
  const [qty, setQty] = useState(1); // the product quantity to be added on cart
  const dispatch = useDispatch();
  const productListDetail = useSelector((state) => state.productDetail);
  const { loading, error, product } = productListDetail;
  
  useEffect(() => {
    dispatch(listProductDetails(id));
  }, [dispatch, id]);

  const addToCartHandler = () => {
    navigate(`/cart/${id}?qty=${qty}`);
  };

  return (
    <ListGroup> 
      <ListGroup.Item>
        <Row>
          <Col> Qty </Col>
          <Col xs="auto" className="my-1"> 
            <Form.Control
              as="select"
              value={qty} 
              onChange={(e) => setQty(e.target.value)}
            >
              {[...Array(product.countInStock).keys()]
                .map((x) => (
                  <option key={x + 1} value={x + 1}> {x + 1} </option>
                )
              )}
            </Form.Control>
          </Col>
        </Row>
      </ListGroup.Item>
      <ListGroup.Item> 
        <Button onClick={addToCartHandler} type="button"> Add to Cart </Button> 
      </ListGroup.Item>
    </ListGroup>
  )
}

here when you click on 'Add to Cart' button, it will add the item and navigate to the cart list view.

Header.js

import React from 'react';
import { Navbar, Nav, Container } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

function Header() {
  return (
    <header >
      <Navbar bg="dark" variant="dark" expand="lg" collapseOnSelect>
        <Container>
          <LinkContainer to="/cart">
            <Nav.Link>
              <i className="fas fa-shopping-cart"></i>
              Cart
            </Nav.Link>
          </LinkContainer>
        </Container>
      </Navbar>
    </header>
  );
}
export default Header;

Here when the user clicks on the 'cart' link, it must navigate to and show all stored items in the cart list, but shows nothing and in the browser's console it warns 'router.ts:11 No routes matched location "/cart/"' message as shown below.

enter image description here

cartScreen.js

import React, { useEffect } from 'react';
import { Col,ListGroup,Row,Image,Button,Card,Form} from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate, useParams, Link, Outlet } from 'react-router-dom';
import { addToCart } from '../actions/cartAction';
import Message from '../components/Message';

const CartScreen = () => {
  // "useLocation" Returns the current location object, which represents the current URL in web browsers.
  const { search } = useLocation();

  const { productid } = useParams();

  const qty = search ? Number(search.split('=')[1]) : 1;
  

  const dispatch = useDispatch();

  const cart = useSelector((state) => state.cart);

  const { cartItems } = cart;

  useEffect(() => {
    dispatch(addToCart(productid, qty));
  }, [dispatch, productid, qty]);

  return (
  
    <Row>
      <Col md={8}>
        {cartItems.length === 0 ? (
          <Message variant="info">
            {' '}
            Go Back To Home Page <Link to="/"></Link>
          </Message>
        ) : (
          <ListGroup>
            {cartItems.map((x) => (
              <ListGroup.Item key={x.product}>
                {x.name} , {x.qty}
              </ListGroup.Item>
            ))}
          </ListGroup>
        )}
      </Col>
      <Col md={4}></Col>
    </Row>
   
  );
};

export default CartScreen;

  • Are you trying to render something on path `"/cart"`? That is the link in the last snippet you are pointing to from the `Header` component. There's no route for `path="/cart"`. *What* do you want rendered on `"/cart"`? Side question: why aren't you dispatching actions to add items to the cart state? – Drew Reese Jul 05 '22 at 18:57
  • Yes, the issue is when I navigate to '/cart/' it shows nothing and 'No routes matched location "/cart/"' warning message comes on the console. About the redux part, I don't have any issue and dispatching actions going ok. – rafee muhammed Jul 05 '22 at 20:16
  • There is no route rending content for `"/cart"`, or `"/cart/"`, so the error message and blank page makes sense. That wasn't what I was asking though. What do you want rendered on `"/cart"`? I can only guess from here that you want the `CartScreen` component to render on `"/cart/"` and that when it's rendered on `"/cart/:productid"` it's taking the `productid` route param and the `qty` queryString param and somehow updating the cart value? – Drew Reese Jul 05 '22 at 20:23
  • Hello, yes on "/cart/" i want to render the added items of cart, thanks for your respone – rafee muhammed Jul 05 '22 at 20:36
  • 1
    Is that the `CartScreen` component then? Can you edit your post to include all relevant code you are trying to work with? – Drew Reese Jul 05 '22 at 20:46
  • as shown above i have add the cartScreen.js as well on the post – rafee muhammed Jul 05 '22 at 21:03

1 Answers1

1

It seems you want the CartScreen component to render on both the "/cart/:productid" path (in the case of updating the cart) and also on the "/cart" path when just viewing the cart.

For this you need to render a Route for each. Here I've structured a layout route on path="/cart" that renders two nested routes: an index route rendering a CartScreen on "." and another CartScreen on ":productid".

<Routes>
  <Route path="/" element={<HomeScreen />} />
  <Route path="/product/:id" element={<ProductScreen />} />
  <Route path="/cart">
    <Route path=":productid" element={<CartScreen />} />
    <Route index element={<CartScreen />} />
  </Route>
</Routes>

Note: You could equally render two routes individually we well if desired.

<Routes>
  <Route path="/" element={<HomeScreen />} />
  <Route path="/product/:id" element={<ProductScreen />} />
  <Route path="/cart:productid" element={<CartScreen />} />
  <Route path="/cart" element={<CartScreen />} />
</Routes>

The CartScreen should handle the queryString in a more react-router-dom@6 way. Instead of using the useLocation hook to access the location.search property, and then applying some "string logic", use the useSearchParams hook to directly access a searchParams object and get the exact queryString parameter value needed.

Example:

const CartScreen = () => {
  const { productid } = useParams();
  const [searchParams, setSearchParams] = useSearchParams();
  const dispatch = useDispatch();
  const { cartItems } = useSelector((state) => state.cart);

  const qty = searchParams.get("qty") || 1;

  useEffect(() => {
    if (qty) {
      // update cart state
      dispatch(addToCart(productid, qty));

      // clear qty queryString param, then "navigate"
      searchParams.delete("qty");
      setSearchParams(searchParams);
    }
  }, [dispatch, productid, qty, searchParams, setSearchParams]);

  return (
    <Row>
      <Col md={8}>
        {!cartItems.length ? (
          <Message variant="info">
            Go Back To Home Page <Link to="/"></Link>
          </Message>
        ) : (
          <ListGroup>
            {cartItems.map((x) => (
              <ListGroup.Item key={x.product}>
                {x.name} , {x.qty}
              </ListGroup.Item>
            ))}
          </ListGroup>
        )}
      </Col>
      <Col md={4}></Col>
    </Row>
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • The problem has been solved thank you, but in the console for '/cart/' path, it gives an error ' GET http://localhost:3000/products/undefined 500 (Internal Server Error)' – rafee muhammed Jul 05 '22 at 21:33
  • @rafeemuhammed When exactly does that error occur in the flow of updating a cart item quantity? It's still not clear to me at all where this `"/cart/"` path is coming from. There is no link you've shared linking to `"/cart/"` and nothing redirecting to it. There's a `navigate(\`/cart/${id}?qty=${qty}\`);`... is the `id` undefined? – Drew Reese Jul 05 '22 at 21:53
  • When clicking on cart link button on Header.js => ``` Cart ``` – rafee muhammed Jul 05 '22 at 22:02
  • @rafeemuhammed That links to `"/cart"` though. Is there something else in your app that is possibly bouncing the URL path to `"/cart/<... something ...>"` where the "something" is undefined? – Drew Reese Jul 05 '22 at 22:06
  • It also gives this error 'Uncaught (in promise) AxiosError{obj}'. And one more thing as shown on the post, in router dom v6 we no longer use optional ? mark after the path's paramter. – rafee muhammed Jul 05 '22 at 22:23
  • @rafeemuhammed The axios issue certainly seems extra-curricular, it might be better to post a new question on SO specific to that issue. If you do post a new question feel free to ping me here in a comment with a link to the new post. And correct, RRDv6 doesn't have optional path parameters. – Drew Reese Jul 05 '22 at 22:27