Search code examples
reactjsreact-routerreact-router-dom

Error related to useSubmit() in React Router DOM Form component


I'm encountering an error in my React application while using the Form component provided by React Router DOM. The error message references useSubmit(), even though I'm not explicitlyit using this function in my code. Can anyone help me understand why this error is occurring and how I can resolve it? I've checked my code, and I'm not using useSubmit() anywhere.

Also I am using a data router(createBrowserRouter)

I am also using Form component in multiple places of my app and I am not getting any error there.

Error message:
Uncaught Error: useSubmit must be used within a data router.

If I am using the HTML form I do not get this error.

Data Router

import { Fragment } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { useSelector } from "react-redux";

import Cart from "./components/Cart/Cart";
import RootLayout from "./components/RootLayout";
import HomePage from "./pages/HomePage";
import SignupPage from "./pages/SignupPage";
import signUpAction from "./Utility/ActionFunctions/signupAction";
import LoginPage from "./pages/LoginPage";
import loginAction from "./Utility/ActionFunctions/loginAction";
import AdminPage from "./pages/AdminPage";
import fetchMealsLoader from "./Utility/LoaderFunctions/fetchMealsLoader";
import adminMealsLoader from "./Utility/LoaderFunctions/adminMealsLoader";
import sendMealDataAction from "./Utility/ActionFunctions/sendDataMealAction";
import OrdersPage from "./pages/OrdersPage";
import fetchOrdersLoader from "./Utility/LoaderFunctions/fetchOrdersLoader";
import UserMenu from "./components/UserMenu";
import useAuth from "./Utility/use-auth";
import sendOrderDataAction from "./Utility/ActionFunctions/sendOrderDataAction";

function App() {
  const { isLoggedIn } = useAuth();

  const cartIsShown = useSelector((state) => state.modalState.cartIsShown);
  const userMenuIsShown = useSelector(
    (state) => state.modalState.userMenuIsShown
  );

  const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: [
        {
          index: true,
          element: <HomePage />,
          loader: fetchMealsLoader,
          action: sendOrderDataAction,
        },
        { path: "auth/signup", element: <SignupPage />, action: signUpAction },
        { path: "auth/login", element: <LoginPage />, action: loginAction },
        {
          path: "admin",
          element: <AdminPage />,
          loader: adminMealsLoader,
          action: sendMealDataAction,
        },
        {
          path: "orders",
          element: <OrdersPage />,
          loader: fetchOrdersLoader,
        },
      ],
    },
  ]);

  return (
    <Fragment>
      {cartIsShown && <Cart />}
      {userMenuIsShown && isLoggedIn && <UserMenu />}
      <RouterProvider router={router} />
    </Fragment>
  );
}

export default App;

Error Component

import { useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Form } from "react-router-dom";

import { modalStateActions, cartAddActions } from "../../store/cart-state";
import { isEmpty, isFiveChars } from "../../Utility/Validator";
import classes from "./Checkout.module.css";

const Checkout = (props) => {
  const dispatch = useDispatch();

  const items = useSelector((state) => state.cartAdd.items);
  const totalAmount = useSelector((state) => state.cartAdd.totalAmount);

  const [formInputsValidity, setFormInputsValidity] = useState({
    name: true,
    street: true,
    city: true,
    postalCode: true,
  });

  const streetInputRef = useRef();
  const postalCodeInputRef = useRef();
  const cityInputRef = useRef();

  const confirmHandler = (event) => {
    event.preventDefault();

    const enteredStreet = streetInputRef.current.value;
    const enteredPostalCode = postalCodeInputRef.current.value;
    const enteredCity = cityInputRef.current.value;

    const enteredStreetIsValid = !isEmpty(enteredStreet);
    const enteredCityIsValid = !isEmpty(enteredCity);
    const enteredPostalCodeIsValid = isFiveChars(enteredPostalCode);

    setFormInputsValidity({
      street: enteredStreetIsValid,
      city: enteredCityIsValid,
      postalCode: enteredPostalCodeIsValid,
    });

    const formIsValid =
      enteredStreetIsValid && enteredCityIsValid && enteredPostalCodeIsValid;

    if (!formIsValid) {
      return;
    }

    // props.onConfirm({
    //   street: enteredStreet,
    //   city: enteredCity,
    //   postalCode: enteredPostalCode,
    // });

    dispatch(cartAddActions.clearCart());
  };

  const streetControlClasses = `${classes.control} ${
    formInputsValidity.street ? "" : classes.invalid
  }`;
  const postalCodeControlClasses = `${classes.control} ${
    formInputsValidity.postalCode ? "" : classes.invalid
  }`;
  const cityControlClasses = `${classes.control} ${
    formInputsValidity.city ? "" : classes.invalid
  }`;

  const order = items.map((item) => ({
    mealId: item.id,
    quantity: item.quantity,
  }));

  const closeCartHandler = () => {
    dispatch(modalStateActions.hide("cart"));
  };

  return (
    <Form method="POST" className={classes.form} onSubmit={confirmHandler}>
      <h2>Address Details</h2>
      <div className={streetControlClasses}>
        <div className={cityControlClasses}>
          <label htmlFor="city">City</label>
          <input type="text" name="city" id="city" ref={cityInputRef} />
          {!formInputsValidity.city && <p>Please enter a valid city!</p>}
        </div>
        <label htmlFor="street">Street</label>
        <input type="text" name="street" id="street" ref={streetInputRef} />
        {!formInputsValidity.street && <p>Please enter a valid street!</p>}
      </div>
      <div className={postalCodeControlClasses}>
        <label htmlFor="postal">Postal Code</label>
        <input type="text" name="postal" id="postal" ref={postalCodeInputRef} />
        {!formInputsValidity.postalCode && (
          <p>Please enter a valid postal code (5 characters long)!</p>
        )}
      </div>
      <input type="hidden" name="order" value={order} />
      <input type="hidden" name="totalAmount" value={totalAmount} />
      <div className={classes.actions}>
        <button type="button" onClick={closeCartHandler}>
          Cancel
        </button>
        <button type="submit" className={classes.submit}>
          Confirm
        </button>
      </div>
    </Form>
  );
};

export default Checkout;

Solution

  • I suspect it is that this Checkout component is rendered in the Cart component which is rendered outside the routing context. To address the issue of using the useSubmit via the Form component the solution is to move the code/logic/etc into the router.

    Example:

    const AppLayout = () => {
      const { isLoggedIn } = useAuth();
    
      const {
        cartIsShown,
        userMenuIsShown
      } = useSelector((state) => state.modalState);
    
      return (
        <Fragment>
          {cartIsShown && <Cart />}
          {userMenuIsShown && isLoggedIn && <UserMenu />}
          <Outlet />
        </Fragment>
      );
    };
    
    // Move router outside the React tree so it's provided as a stable reference!
    const router = createBrowserRouter([
      {
        element: <AppLayout />,
        children: [
          {
            element: <RootLayout />,
            children: [
              {
                index: true,
                element: <HomePage />,
                loader: fetchMealsLoader,
                action: sendOrderDataAction,
              },
              {
                path: "auth",
                children: [
                  {
                    path: "signup",
                    element: <SignupPage />,
                    action: signUpAction
                  },
                  {
                    path: "login",
                    element: <LoginPage />,
                    action: loginAction
                  },
                ],
              },
              {
                path: "admin",
                element: <AdminPage />,
                loader: adminMealsLoader,
                action: sendMealDataAction,
              },
              {
                path: "orders",
                element: <OrdersPage />,
                loader: fetchOrdersLoader,
              },
            ],
          },
        ],
      },
    ]);
    
    function App() {
      return <RouterProvider router={router} />;
    }
    
    export default App;