Formdata Types
Created note: Form Data Types
There are 3 different form data types
null- value doesn’t exist in the form data at allstring- Value returned by inputsFile- applies to file inputs
Button Forms
Created note: How to use Submit Buttons efficiently
When submitting forms on a client we usually submit values using onClick However, it does not help to cover some edge cases such as:
- double click and stopping the action
- double clicking and the other requests finishing his job much sooner which results in throwing an error but the last request.
For that reason, It is a good idea to wrap a button with aFormand let it do its job. Then, use an action to read a request, check values and then act accordingly
If you have multiple buttons that trigger an action, you have to have a way of checking that. for that reason, you can use name and value properties
Example
<Button name="intent" value="delete">Test</Button> Scripting
In general, Remix can work without JavaScript but sometimes there are things that we want to possibly do on a client (prefetching, otpimistic UI etc). For that reason, it is handy to allow adding client side javascript. For that reason, you have to use
Scroll Restoration
Note Created: Remix - ScrollRestoration
In Remix you can use <SCrollRestoration /> component and add it to a root.tsx file. However, now every action or redirect will reset the scroll. To prevent that on every redirect, you have to use preventScrollRestoration prop on <Form /> and <Link /> components.
SEO
<title>tag is what is within the<head>element and it is used to display a title in a browser tab, and a search engine. NC:[[|<title>]] <meta>provides meta data about the page like description. NC: HTML meta tagsdescriptionis to provide a desription of a page meta description- viewport
name="viewport" content="width=device-width, initial-scale=1"which tells the browser to set a width as a device with and scale to 1 NC: meta viewport charsetto specify the char encoding of the page. It should always be that, unless you want to do something weird. NC: meta charset
learn more about meta tags on seosherpa.com
In Remix, you can set Meta tags that way NC: Setting meta tags in Remix
import { type MetaFunction } from '@remix-run/react'
export const meta: MetaFunction = () => {
return [
{ title: 'Sandwich Shop' },
{ name: 'description', content: 'Fill your tummy with something yummy' },
]
} Also, because you are responsible for everything between head tags, you also have to add <Meta /> component in your root.tsx file and make sure that they are rendered. NC: You are responsible for everything between head tags (Remix)
You can also set values dynamically NC: Setting meta tags in Remix
import { json } from '@remix-run/node'
import { type MetaFunction } from '@remix-run/react'
import { getSandwich } from '../sandwiches.server'
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data.sandwich.name },
{ name: 'description', content: data.sandwich.description },
]
}
export function loader({ params }) {
return json({ sandwich: getSandwich(params.sandwichId) })
} Error Handling
In remix, I can create an error boundary by creating it within a route and exporting it like so NC: Error Boundaries in Remix
export function ErrorBoundary() {
const error = useRouteError()
console.error(error)
return (
<div className="ontainer mx-auto flex h-full w-full items-center justify-center bg-destructive p-20 text-h2 text-destructive-foreground">
<h1>Oh no!</h1>
<p>Something bad happend!!!!!</p>
</div>
)
} NC: Code Snippet - Reusable Error Boundary
import {
isRouteErrorResponse,
useParams,
useRouteError,
} from '@remix-run/react'
import { type ErrorResponse } from '@remix-run/router'
import { getErrorMessage } from '#app/utils/misc.tsx'
type StatusHandler = (info: {
error: ErrorResponse
params: Record<string, string | undefined>
}) => JSX.Element | null
export function GeneralErrorBoundary({
defaultStatusHandler = ({ error }) => (
<p>
{error.status} {error.data}
</p>
),
statusHandlers,
unexpectedErrorHandler = error => <p>{getErrorMessage(error)}</p>,
}: {
defaultStatusHandler?: StatusHandler
statusHandlers?: Record<number, StatusHandler>
unexpectedErrorHandler?: (error: unknown) => JSX.Element | null
}) {
const error = useRouteError()
const params = useParams()
if (typeof document !== 'undefined') {
console.error(error)
}
return (
<div className="container mx-auto flex h-full w-full items-center justify-center bg-destructive p-20 text-h2 text-destructive-foreground">
{isRouteErrorResponse(error)
? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
error,
params,
})
: unexpectedErrorHandler(error)}
</div>
)
} export function ErrorBoundary() {
return (
<GeneralErrorBoundary
statusHandlers={{
403: ({ params }) => (
<p>You're not authorized to look at {params.sandwichId}</p>
),
}}
/>
)
} NC: Code Snippet - getErrorMessage
/**
* Does its best to get a string error message from an unknown error.
*/
export function getErrorMessage(error: unknown) {
if (typeof error === 'string') return error
if (
error &&
typeof error === 'object' &&
'message' in error &&
typeof error.message === 'string'
) {
return error.message
}
console.error('Unable to get error message for error', error)
return 'Unknown Error'
} Root Component Error
In Remix, I have to make sure that css, js etc is loaded for error boundary and the app component. For that reason, it is good to create a Document component that will have everything within <html> tag, apart from the visual bits within <body>
export async function loader() {
throw new Error('🐨 root loader error')
return json({ username: os.userInfo().username, ENV: getEnv() })
}
// 🐨 Create a Document component here that renders almost everything that's in
// the App with the exception of the visual stuff in the body. It should not
// use useLoaderData because we can't rely on that in the error case.
function Document({ children }: PropsWithChildren) {
return (
<html lang="en" className="h-full overflow-x-hidden">
<head>
<Meta />
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Links />
</head>
<body className="flex h-full flex-col justify-between bg-background text-foreground">
{children}
</body>
</html>
)
}
export default function App() {
// throw new Error('🐨 root component error')
const data = useLoaderData<typeof loader>()
// 🐨 replace most of this with the <Document> component and render the
// header, outlet, and footer inside of it.
return (
<Document>
<header className="container mx-auto py-6">
<nav className="flex justify-between">
<Link to="/">
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="users/kody/notes/d27a197e">
Kody's Notes
</Link>
</nav>
</header>
<div className="flex-1">
<Outlet />
</div>
<div className="container mx-auto flex justify-between">
<Link to="/">
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<p>Built with ♥️ by {data.username}</p>
</div>
<div className="h-5" />
<ScrollRestoration />
<script
dangerouslySetInnerHTML={{
__html: `window.ENV = ${JSON.stringify(data.ENV)}`,
}}
/>
<Scripts />
<KCDShop />
<LiveReload />
</Document>
)
}
export const meta: MetaFunction = () => {
return [
{ title: 'Epic Notes' },
{ name: 'description', content: `Your own captain's log` },
]
}
export function ErrorBoundary() {
// 🐨 render the GeneralErrorBoundary in your new Document component.
return (
<Document>
<GeneralErrorBoundary />
</Document>
)
} Not Found
NC: Not Found Error in Remix
To handle not found errors, you have to create a splat route that will match any route that does not exist and you can do it by creating a file app/routes/$.tsx.
Inside of this file, you have to create a loader, that throws 404
export function loader() {
throw new Response('Not found', { status: 404 })
}```
Next, you have to return error boundary
```ts
export function ErrorBoundary() {
const location = useLocation()
return (
<GeneralErrorBoundary
statusHandlers={{
404: () => (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-3">
<h1>We can't find this page:</h1>
<pre className="text-body-lg whitespace-pre-wrap break-all">
{location.pathname}
</pre>
</div>
<Link to="/" className="text-body-md underline">
Back to home
</Link>
</div>
),
}}
/>
)
} And, just in case, return that error boundary
export default function NotFound() {
// due to the loader, this component will never be rendered, but we'll return
// the error boundary just in case.
return <ErrorBoundary />
}