Skip to content

Commit 41e6374

Browse files
committed
Add form validation
1 parent 30c192f commit 41e6374

File tree

3 files changed

+139
-26
lines changed

3 files changed

+139
-26
lines changed

remix-jokes/app/routes/jokes/$jokeId.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const loader: LoaderFunction = async ({ params }) => {
1717
if (!joke) throw new Error("Joke not found");
1818

1919
const data: LoaderData = { joke };
20-
return json(data);
20+
return json(data, { status: 200 });
2121
};
2222

2323
export default function JokeRoute() {

remix-jokes/app/routes/jokes/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export default function JokesIndexRoute() {
2828
return (
2929
<div>
3030
<p>Here's a random joke:</p>
31-
<p>{joke.content}</p>
31+
<em>{joke.content}</em>
32+
<br />
3233
<Link to={joke.id}>"{joke.name}" Permalink</Link>
3334
</div>
3435
);

remix-jokes/app/routes/jokes/new.tsx

Lines changed: 136 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,139 @@
1+
import { json, redirect } from "@remix-run/node";
2+
import { useActionData } from "@remix-run/react";
3+
import { db } from "~/utils/db.server";
4+
5+
import type { Joke } from "@prisma/client";
6+
import type { ActionFunction } from "@remix-run/node";
7+
8+
type ActionData = {
9+
fields?: { name: string; content: string };
10+
formError?: string;
11+
fieldErrors?: {
12+
name: string | undefined;
13+
content: string | undefined;
14+
};
15+
};
16+
17+
const validateJokeContent = (content: string) => {
18+
if (content.length < 10) {
19+
return `That joke is too short`;
20+
}
21+
};
22+
23+
const validateJokeName = (name: string) => {
24+
if (name.length < 3) {
25+
return `That joke's name is too short`;
26+
}
27+
};
28+
29+
const badRequest = (data: ActionData) => json(data, { status: 400 });
30+
31+
const action: ActionFunction = async ({ request }) => {
32+
const form = await request.formData();
33+
const name = form.get("name");
34+
const content = form.get("content");
35+
36+
// we do this type check to be extra sure and to make TypeScript happy
37+
// we'll explore validation next!
38+
if (typeof name !== "string" || typeof content !== "string") {
39+
return badRequest({ formError: `Form not submitted correctly!` });
40+
}
41+
42+
const fieldErrors = {
43+
name: validateJokeName(name),
44+
content: validateJokeContent(content),
45+
};
46+
47+
const fields = { name, content };
48+
49+
if (Object.values(fieldErrors).some(Boolean)) {
50+
return badRequest({ fieldErrors, fields });
51+
}
52+
53+
const joke: Joke = await db.joke.create({
54+
data: fields,
55+
});
56+
57+
return redirect(`/jokes/${joke.id}`);
58+
};
59+
160
function NewJoke() {
2-
return (
3-
/* Form with name and content fields */
4-
<div>
5-
<p>Add your own hilarious joke</p>
6-
<form method="post">
7-
<div>
8-
<label>
9-
Name: <input type="text" name="name" />
10-
</label>
11-
</div>
12-
<div>
13-
<label>
14-
Content: <textarea name="content" />
15-
</label>
16-
</div>
17-
<div>
18-
<button type="submit" className="button">
19-
Add
20-
</button>
21-
</div>
22-
</form>
23-
</div>
24-
)
61+
const actionData = useActionData<ActionData>();
62+
63+
return (
64+
/* Form with name and content fields */
65+
<div>
66+
<p>Add your own hilarious joke</p>
67+
<form method="post">
68+
<div>
69+
<label>
70+
Name:{" "}
71+
<input
72+
type="text"
73+
defaultValue={actionData?.fields?.name}
74+
name="name"
75+
aria-invalid={
76+
Boolean(actionData?.fieldErrors?.name) ||
77+
undefined
78+
}
79+
aria-errormessage={
80+
actionData?.fieldErrors?.name
81+
? "name-error"
82+
: undefined
83+
}
84+
/>
85+
</label>
86+
{actionData?.fieldErrors?.name ? (
87+
<p
88+
className="form-validation-error"
89+
role="alert"
90+
id="name-error"
91+
>
92+
{actionData.fieldErrors.name}
93+
</p>
94+
) : null}
95+
</div>
96+
<div>
97+
<label>
98+
Content:{" "}
99+
<textarea
100+
defaultValue={actionData?.fields?.content}
101+
name="content"
102+
aria-invalid={
103+
Boolean(actionData?.fieldErrors?.content) ||
104+
undefined
105+
}
106+
aria-errormessage={
107+
actionData?.fieldErrors?.content
108+
? "content-error"
109+
: undefined
110+
}
111+
/>
112+
</label>
113+
{actionData?.fieldErrors?.content ? (
114+
<p
115+
className="form-validation-error"
116+
role="alert"
117+
id="content-error"
118+
>
119+
{actionData.fieldErrors.content}
120+
</p>
121+
) : null}
122+
</div>
123+
<div>
124+
{actionData?.formError ? (
125+
<p className="form-validation-error" role="alert">
126+
{actionData.formError}
127+
</p>
128+
) : null}
129+
<button type="submit" className="button">
130+
Add
131+
</button>
132+
</div>
133+
</form>
134+
</div>
135+
);
25136
}
26137

27-
export default NewJoke
138+
export default NewJoke;
139+
export { action };

0 commit comments

Comments
 (0)