t Data Mutation

Formdata Types

Created note: Form Data Types
There are 3 different form data types

  • null - value doesn’t exist in the form data at all
  • string - Value returned by inputs
  • File - 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 a Form and 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 and

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 tags

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 />  
}