7e76c353b3
### What? I kindly request the maintainers to review this Pull Request, which aims to migrate the `with-mongoose` example to TypeScript with total type safety. ### Why? By doing so, we enhance the overall quality and maintainability of the example, aligning it with modern best practices for type-safe codebases. ### How? I have thoroughly tested the changes to ensure they do not introduce regressions and maintain compatibility with the existing codebase. Co-authored-by: Balázs Orbán <18369201+balazsorban44@users.noreply.github.com>
232 lines
5.4 KiB
TypeScript
232 lines
5.4 KiB
TypeScript
import { useState } from 'react'
|
|
import { useRouter } from 'next/router'
|
|
import { mutate } from 'swr'
|
|
|
|
interface FormData {
|
|
name: string
|
|
owner_name: string
|
|
species: string
|
|
age: number
|
|
poddy_trained: boolean
|
|
diet: string[]
|
|
image_url: string
|
|
likes: string[]
|
|
dislikes: string[]
|
|
}
|
|
|
|
interface Error {
|
|
name?: string
|
|
owner_name?: string
|
|
species?: string
|
|
image_url?: string
|
|
}
|
|
|
|
type Props = {
|
|
formId: string
|
|
petForm: FormData
|
|
forNewPet?: boolean
|
|
}
|
|
|
|
const Form = ({ formId, petForm, forNewPet = true }: Props) => {
|
|
const router = useRouter()
|
|
const contentType = 'application/json'
|
|
const [errors, setErrors] = useState({})
|
|
const [message, setMessage] = useState('')
|
|
|
|
const [form, setForm] = useState({
|
|
name: petForm.name,
|
|
owner_name: petForm.owner_name,
|
|
species: petForm.species,
|
|
age: petForm.age,
|
|
poddy_trained: petForm.poddy_trained,
|
|
diet: petForm.diet,
|
|
image_url: petForm.image_url,
|
|
likes: petForm.likes,
|
|
dislikes: petForm.dislikes,
|
|
})
|
|
|
|
/* The PUT method edits an existing entry in the mongodb database. */
|
|
const putData = async (form: FormData) => {
|
|
const { id } = router.query
|
|
|
|
try {
|
|
const res = await fetch(`/api/pets/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
Accept: contentType,
|
|
'Content-Type': contentType,
|
|
},
|
|
body: JSON.stringify(form),
|
|
})
|
|
|
|
// Throw error with status code in case Fetch API req failed
|
|
if (!res.ok) {
|
|
throw new Error(res.status.toString())
|
|
}
|
|
|
|
const { data } = await res.json()
|
|
|
|
mutate(`/api/pets/${id}`, data, false) // Update the local data without a revalidation
|
|
router.push('/')
|
|
} catch (error) {
|
|
setMessage('Failed to update pet')
|
|
}
|
|
}
|
|
|
|
/* The POST method adds a new entry in the mongodb database. */
|
|
const postData = async (form: FormData) => {
|
|
try {
|
|
const res = await fetch('/api/pets', {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: contentType,
|
|
'Content-Type': contentType,
|
|
},
|
|
body: JSON.stringify(form),
|
|
})
|
|
|
|
// Throw error with status code in case Fetch API req failed
|
|
if (!res.ok) {
|
|
throw new Error(res.status.toString())
|
|
}
|
|
|
|
router.push('/')
|
|
} catch (error) {
|
|
setMessage('Failed to add pet')
|
|
}
|
|
}
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
) => {
|
|
const target = e.target
|
|
const value =
|
|
target.name === 'poddy_trained'
|
|
? (target as HTMLInputElement).checked
|
|
: target.value
|
|
const name = target.name
|
|
|
|
setForm({
|
|
...form,
|
|
[name]: value,
|
|
})
|
|
}
|
|
|
|
/* Makes sure pet info is filled for pet name, owner name, species, and image url*/
|
|
const formValidate = () => {
|
|
let err: Error = {}
|
|
if (!form.name) err.name = 'Name is required'
|
|
if (!form.owner_name) err.owner_name = 'Owner is required'
|
|
if (!form.species) err.species = 'Species is required'
|
|
if (!form.image_url) err.image_url = 'Image URL is required'
|
|
return err
|
|
}
|
|
|
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault()
|
|
const errs = formValidate()
|
|
|
|
if (Object.keys(errs).length === 0) {
|
|
forNewPet ? postData(form) : putData(form)
|
|
} else {
|
|
setErrors({ errs })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<form id={formId} onSubmit={handleSubmit}>
|
|
<label htmlFor="name">Name</label>
|
|
<input
|
|
type="text"
|
|
maxLength={20}
|
|
name="name"
|
|
value={form.name}
|
|
onChange={handleChange}
|
|
required
|
|
/>
|
|
|
|
<label htmlFor="owner_name">Owner</label>
|
|
<input
|
|
type="text"
|
|
maxLength={20}
|
|
name="owner_name"
|
|
value={form.owner_name}
|
|
onChange={handleChange}
|
|
required
|
|
/>
|
|
|
|
<label htmlFor="species">Species</label>
|
|
<input
|
|
type="text"
|
|
maxLength={30}
|
|
name="species"
|
|
value={form.species}
|
|
onChange={handleChange}
|
|
required
|
|
/>
|
|
|
|
<label htmlFor="age">Age</label>
|
|
<input
|
|
type="number"
|
|
name="age"
|
|
value={form.age}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<label htmlFor="poddy_trained">Potty Trained</label>
|
|
<input
|
|
type="checkbox"
|
|
name="poddy_trained"
|
|
checked={form.poddy_trained}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<label htmlFor="diet">Diet</label>
|
|
<textarea
|
|
name="diet"
|
|
maxLength={60}
|
|
value={form.diet}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<label htmlFor="image_url">Image URL</label>
|
|
<input
|
|
type="url"
|
|
name="image_url"
|
|
value={form.image_url}
|
|
onChange={handleChange}
|
|
required
|
|
/>
|
|
|
|
<label htmlFor="likes">Likes</label>
|
|
<textarea
|
|
name="likes"
|
|
maxLength={60}
|
|
value={form.likes}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<label htmlFor="dislikes">Dislikes</label>
|
|
<textarea
|
|
name="dislikes"
|
|
maxLength={60}
|
|
value={form.dislikes}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<button type="submit" className="btn">
|
|
Submit
|
|
</button>
|
|
</form>
|
|
<p>{message}</p>
|
|
<div>
|
|
{Object.keys(errors).map((err, index) => (
|
|
<li key={index}>{err}</li>
|
|
))}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default Form
|