Search code examples
javascriptreactjsreact-hooksreact-reduxreact-router-dom

getting id from url in reactRouter v6


I am following an ecommerce tutorial, which was done with react V5 and I'm trying to remake it in react V6. Problem I faced includes url. I am building Order details page, which should contain details about the placed order. After clicking "Place Order" button I am getting an error, saying: tched leaf route at location "/order/34" does not have an element. This means it will render an with a null value by default resulting in an "empty" page.

OrderScreen.js:

import React, { useState, useEffect } from 'react'
import { Form, Button, Row, Col, ListGroup, Image, Card, ListGroupItem } from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
import Message from '../components/Message'
import Loader from '../components/Loader'
import { Redirect, Link, useNavigate, useParams, useLocation, useMatch } from 'react-router-dom'
import { getOrderDetails } from '../actions/orderActions'
import { ORDER_CREATE_RESET } from '../constants/orderConstants'

function OrderScreen({match}) {


    
    const orderId = match.params.id

    const orderDetails = useSelector(state => state.orderDetails)
    const { order, error, loading } = orderDetails

    const navigate = useNavigate()

    const dispatch = useDispatch()
    if(!loading && !error){
        order.itemsPrice = order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0).toFixed(2)
    }
    

    useEffect(() => {
    if(!order || order._id !== Number(orderId)){
            dispatch(getOrderDetails(orderId))
            console.log("yes")
        }
    }, [dispatch,navigate,order,orderId])

    return loading ? (
        <Loader/>
    ) : error ? (
        <Message variant='danger'>{error}</Message>
    ) : (
            <div>
                <Row>
                <Col md={8}>
                    <ListGroup variant='flush'>
                    <ListGroup.Item>
                        <h2>Shipping</h2>

                        <p>
                        <strong>Shipping: </strong>
                        {order.shippingAddress.address}, {order.shippingAddress.city},
                        {'  '}
                        {order.shippingAddress.postalCode},
                        {'  '}
                        {order.shippingAddress.country}.
                        </p>
                    </ListGroup.Item>

                    <ListGroup.Item>
                        <h2>Payment Method</h2>

                        <p>
                        <strong>Method: </strong>
                        {order.paymentMethod}
                        </p>
                    </ListGroup.Item>

                    <ListGroup.Item>
                        <h2>Order Items</h2>
                        {order.orderItems.length===0 ? <Message variant='info'>
                        Your order is empty
                        </Message> : (
                        <ListGroup variant='flush'>
                            {order.orderItems.map((item, index) => (
                            <ListGroup.Item key={index}>
                                <Row>
                                <Col md={1}>
                                    <Image src={item.image} alt={item.name} fluid rounded></Image>
                                </Col>

                                <Col>
                                    <Link to={'/product/'+item.product}>{item.name}</Link>
                                </Col>

                                <Col md={4}>
                                    {item.qty} X ${item.price} = ${(item.qty * item.price).toFixed(2)}
                                </Col>
                                </Row>
                            </ListGroup.Item>
                            ))}
                        </ListGroup>
                        )}
                    </ListGroup.Item>

                    </ListGroup>

                    
                </Col>
                <Col md={4}>
                    <Card>
                    <ListGroup varaint='flush'>
                            <ListGroup.Item>
                                <h2>Order Summary</h2>
                            </ListGroup.Item>

                            <ListGroup.Item>
                                <Row>
                                <Col>Item:</Col>
                                <Col>${order.itemsPrice}</Col>
                                </Row>
                            </ListGroup.Item>

                            <ListGroup.Item>
                                <Row>
                                <Col>Shipping:</Col>
                                <Col>${order.shippingPrice}</Col>
                                </Row>
                            </ListGroup.Item>

                            <ListGroup.Item>
                                <Row>
                                <Col>Tax:</Col>
                                <Col>${order.taxPrice}</Col>
                                </Row>
                            </ListGroup.Item>

                            <ListGroup.Item>
                                <Row>
                                <Col>Total:</Col>
                                <Col>${order.totalPrice}</Col>
                                </Row>
                            </ListGroup.Item>

                            {/* <ListGroup.Item>
                                <Button
                                type='button'
                                className='btn-block'
                                disabled={order.orderItems === 0}
                                onClick={placeOrder}
                                >
                                Place Order
                                </Button>
                            </ListGroup.Item> */}
                    </ListGroup>
                    </Card>
                </Col>
                </Row>
            </div>
            )
}

export default OrderScreen

orderActions.js:

export const getOrderDetails = (id) => async (dispatch, getState) => {
    try{
        dispatch({
            type: ORDER_DETAILS_REQUEST,
        })

        const {
            userLogin: { userInfo },
        } = getState()


        const config = {
            headers:{
                'Content-type': 'application/json',
                Authorization: 'Bearer '+userInfo.token,
            }
        }

        const { data } = await axios.get(
            '/api/orders/'+id,
            config
        )
        
        dispatch({
            type: ORDER_DETAILS_SUCCESS,
            payload: data
        })


    }catch(error){
        dispatch({
            type:ORDER_DETAILS_FAIL,
            payload:error.response && error.response.data.detail
                ? error.response.data.detail
                : error.message,
        })
    }
}

In App.js: I have imported OrderScreen and inserted

<Route path='/order/:id' component={<OrderScreen/>} />

in Routes container.


Solution

  • Matched leaf route at location "/order/34" does not have an element. This means it will render an with a null value by default resulting in an "empty" page.

    The react-router-dom@6 Route component doesn't have any component prop. The Route renders all content on a single element prop. The error is saying you have a Route with no element to render.

    <Route path='/order/:id' element={<OrderScreen />} />
    

    After this, the OrderScreen component will have an issue accessing the id route path param as there are now also not any route props. The above JSX should make it overtly clear that no props are passed to OrderScreen. Use the useParams hook to access the route path params

    ...
    import { ... useParams, ... } from 'react-router-dom';
    ...
    
    function OrderScreen() {
      const dispatch = useDispatch();
      const navigate = useNavigate();
    
      const { id } = useParams();
    
      const { order, error, loading } = useSelector(state => state.orderDetails);
        
      useEffect(() => {
        if (!order || String(order._id) !== id){
          dispatch(getOrderDetails(id))
          console.log("yes")
        }
      }, [dispatch, order, id]);
    
      if (loading) {
        return <Loader />;
      }
    
      if (error) {
        return <Message variant='danger'>{error}</Message>;
      }
    
      // Don't mutate state!! Compute derived total price when rendering.
      const orderItemsPrice = order.orderItems.reduce(
        (acc, item) => acc + item.price * item.qty,
        0
      ).toFixed(2);
    
      return (
        <div>
          ...
        </div>
      );
    }
    
    export default OrderScreen;