I'm new to Remix (and backend programming in general) and feeling lost troubleshooting this. I'm trying to UseFetcher to allow for non-navigational data mutations in a "todo-like" application. Remix docs doesn't explicitly say I need to be using it within a data router, and the examples don't clear up my confusion at all.
Here's what my App component looks like in root.tsx:
export default function App() {
return (
<html lang="en" className="h-full">
<head>
<Meta />
<Links />
</head>
<body className="h-full">
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
And my routes/goods.tsx for displaying a list of items (much of this is adapted from the default Indie Stack):
export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const title = formData.get("title");
const id = formData.get("id");
if (typeof title !== "string" || title.length === 0) {
return json(
{ errors: { title: "Title is required" } },
{ status: 400 }
);
}
const good = await updateGood({ title, id });
return null;
}
export default function GoodsPage() {
const data = useLoaderData<typeof loader>();
const user = useUser();
return (
<div className="flex h-full min-h-screen flex-col">
<Outlet />
<main className="flex h-full bg-white">
<div className="h-full w-80 border-r bg-gray-50">
{data.completedGoodListItems.length === 0 ? (
<p className="p-4">No goods yet</p>
) : (
<>
<h2>Incomplete</h2>
<ol>
{data.completedGoodListItems.map((good) => (
<GoodItem key={good.id} good={good}></GoodItem>
))}
</ol>
</>
)}
<>
<h2>Completed</h2>
<ol>
{data.incompleteGoodListItems.map((good) => (
<GoodItem key={good.id} good={good}></GoodItem>
))}
</ol>
</>
</div>
<Form action="/logout" method="post">
<button
type="submit"
className="rounded bg-slate-600 py-2 px-4 text-blue-100 hover:bg-blue-500 active:bg-blue-600"
>
Logout
</button>
</Form>
</main>
</div>
);
}
function GoodItem ({ good }) {
const fetcher = useFetcher();
return (
<li>
<fetcher.Form method="post">
<input type="text" defaultValue={good.title}></input>
</fetcher.Form>
</li>
)}
This results in Error: useFetcher must be used within a data router.
So then I try to follow the instructions for encapsulating the App within a data router using createBrowserRouter which leads me to writing this code in my root.tsx:
async function loader({ request }: LoaderArgs) {
return json({
user: await getUser(request),
});
}
const router = createBrowserRouter([
{
path: "/",
element: <App />,
// loader: rootLoader,
children: [
{
path: "/goods",
element: <GoodsPage />,
// loader: loaderName,
},
],
},
]);
// @ts-ignore
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
export default function App() {
return (
<html lang="en" className="h-full">
<head>
<Meta />
<Links />
</head>
<body className="h-full">
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
I didn't know what to add for the loaders for each element. I tried assigning the async loader to a const and adding it into the constructor for router, but I received the error: The expected type comes from property 'loader' which is declared here on type 'RouteObject' so I just left it blank.
This code results in ReferenceError: document is not defined certainly because I don't have the syntax or structure correct for this router. Can someone provide some guidance on how I should be using createBrowserRouter in this context? I know I need to use the RouterProvider
component in some way, but I don't have enough experience to see the path forward. What am I missing here?
You are most probably importing useFetcher
from an incorrect package. Make sure that you are importing it from @remix-run/react
:
import { useFetcher } from "@remix-run/react";
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.