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

How to get react router methods in HOC component?


I have created on HOC component in my react application to reuse my code and for some purpose I need search params from the query but the react-router-dom is not accessible inside it and throwing error.

Here is my HOC component

import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { getOrders } from "../../api/overview-controller";

const OrdersListHOC = (WrappedComponent) => {
    const orderListHOCComponent = () => {
        const search = useLocation().search;
        const limit = new URLSearchParams(search).get('limit') || 20;
        const [orders, setOrders] = useState([]);
        const [customerNumber, setCustomerNumber] = useState("");
        const [subcontractor, setSubcontractor] = useState("");

        useEffect (() => {
            fetchOrders()
        }, []);

        const fetchOrders = async() => {
            const searchFilter = {
                customerNumber,
                subcontractor,
                limit
            }
            const orders = await getOrders(searchFilter);
            setOrders(orders);
        }
        return (
            <div>
                <WrappedComponent orders={orders}/>
            </div>
        );
    }
    return orderListHOCComponent;
};

export default OrdersListHOC;

Here is my order list component

const OrdersList = ({ orders }: props) => {
  return (<>
        {orders.map((order) => {
            return (<React.Fragment key={order.order.id}>
            <Grid container justifyContent={"center"} spacing={2}>
                <Grid item>
                    <Typography variant='subtitle1' mb={1/2}>Order number:</Typography>
                    <Typography variant='subtitle1' mb={1/2}>Customer name:</Typography>
                </Grid>
                <Grid item>
                    <Typography variant='h6'>{order.order.kind}{order.order.location.countryCode}{moment(order.order.orderDate, "YYYY-MM-DD").format("YY")}/{order.order.id}</Typography>
                    <Typography variant='h6'>{order.order.customerNumber.name}</Typography>
                </Grid>
            </Grid>
            <Divider light={false}/>
        </React.Fragment>)})}
  </>);
}

export default OrderListHOC(OrdersList);

Error:

Error: useLocation() may be used only in the context of a <Router> component.

Solution

  • The error message isn't exactly unclear, the useLocation hook can only be used within a routing context, e.g. a router. You just need to ensure there's a router upstream, i.e. higher in the ReactTree than any component that needs it. I'd suggest also using the useSearchParams hook instead of constructing your own URLSearchParams object.

    import React, { useEffect, useState } from 'react';
    import { useSearchParams } from 'react-router-dom';
    import { getOrders } from "../../api/overview-controller";
    
    const withOrdersList = (WrappedComponent) => (props) => {
      const [searchParams] = useSearchParams();
      const limit = searchParams.get('limit') || 20;
    
      const [orders, setOrders] = useState([]);
      const [customerNumber, setCustomerNumber] = useState("");
      const [subcontractor, setSubcontractor] = useState("");
    
      useEffect (() => {
        const fetchOrders = async () => {
          const searchFilter = {
            customerNumber,
            subcontractor,
            limit
          }
          const orders = await getOrders(searchFilter);
          setOrders(orders);
        }
    
        fetchOrders();
      }, []);
    
      return <WrappedComponent {...props} orders={orders} />;
    };
    
    export default withOrdersList;
    
    const OrdersList = ({ orders }: props) => {
      return (
        <>
          {orders.map((order) => (
            <React.Fragment key={order.order.id}>
              <Grid container justifyContent={"center"} spacing={2}>
                <Grid item>
                  <Typography variant='subtitle1' mb={1/2}>Order number:</Typography>
                  <Typography variant='subtitle1' mb={1/2}>Customer name:</Typography>
                </Grid>
                <Grid item>
                  <Typography variant='h6'>
                    {order.order.kind}{order.order.location.countryCode}
                    {moment(order.order.orderDate, "YYYY-MM-DD").format("YY")}/{order.order.id}
                  </Typography>
                  <Typography variant='h6'>{order.order.customerNumber.name}</Typography>
                </Grid>
              </Grid>
              <Divider light={false} />
            </React.Fragment>
          ))}
        </>
      );
    }
    
    export default withOrderList(OrdersList);
    
    import { BrowserRouter } from 'react-router-dom';
    import OrderList from '../path/to/OrderList';
    
    ...
    
    <BrowserRouter> // <-- provide routing context to sub-Reactree
      ...
    
      <div>
        <OrderList /> // <-- can access routing context
      </div>
    
      ...
    </BrowserRouter>