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;
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;