Now, let's try to add one more field to our form and database: due_date
with a date picker.
Prepare Database/Controller
I will just show you the code without any comments. These are Laravel fundamentals, I'm sure you know them.
Migration:
php artisan make:migration add_due_date_to_tasks_table
Schema::table('tasks', function (Blueprint $table) { $table->date('due_date')->nullable();});
Then, we run:
php artisan migrate
Adding to Model fillables:
app/Models/Task.php:
class Task extends Model{ use HasFactory; protected $fillable = [ 'name', 'is_completed', 'due_date', ]; protected function casts(): array { return [ 'is_completed' => 'boolean', 'due_date' => 'date' ]; }}
Validation rules:
app/Http/Requests/StoreTaskRequest.php:
// ...public function rules(): array{ return [ 'name' => ['required', 'string', 'max:255'], 'due_date' => ['nullable', 'date'], ];}
app/Http/Requests/UpdateTaskRequest.php:
public function rules(): array{ return [ 'name' => ['required', 'string', 'max:255'], 'is_completed' => ['required', 'boolean'], 'due_date' => ['nullable', 'date'], ];}
No modifications are needed for the Controller since we use $request->validated()
to create/update tasks.
Ok, the back end is ready. It's time to dive into the JavaScript/TypeScript side.
Adding Field to TypeScript Types
We need to update our type in the file of all types:
resources/js/types/index.d.ts
export interface Task { id: number; name: string; is_completed: boolean; due_date?: string; created_at: string; updated_at: string;}
Also, in our Create.tsx
page, we need to add this field to the form's internal types.
resources/js/pages/Tasks/Create.tsx
type CreateTaskForm { name?: string, due_date?: string } export default function Create() { // ... const { data, setData, errors, post, reset, processing } = useForm<Required<CreateTaskForm>>({ name: '', due_date: '', });}
Importing Date Formatting Library
We need a JavaScript dates library called date-fns
to re-format the date in our future input.
npm install date-fns --save
Then, we import it on top of our component.
resources/js/pages/Tasks/Create.tsx
import { type BreadcrumbItem } from '@/types';import { format } from 'date-fns'; type CreateTaskForm = { name?: string, due_date?: string}
Adding the Input Date to the Form
Then, we finally create a new input type="date"
in the form.
<form onSubmit={createTask} className="space-y-6"> <div className="grid gap-2"> <Label htmlFor="name">Task Name *</Label> <Input id="name" ref={taskName} value={data.name} onChange={(e) => setData('name', e.target.value)} className="mt-1 block w-full" /> <InputError message={errors.name} /> </div> <div className="grid gap-2"> <Label htmlFor="name">Due Date</Label> <Input id="due_date" value={data.due_date} onChange={(e) => setData('due_date', format(new Date(e.target.value), 'yyyy-MM-dd'))} className="mt-1 block w-full" type="date" /> <InputError message={errors.due_date} /> </div> // ...
Here's the visual result:
And if we submit the form, the due_date
is successfully saved into the DB:
Wait, Why No Shadcn Date Picker?
Indeed, Shadcn has a component called [Date Picker], but in our form, we used a regular browser input type="date"
.
The problem is that the Shadcn component doesn't currently support the latest React 19. I did try to install it with --force
, but it caused issues when re-installing the project on the other server.
Under the hood, the Shadcn component uses another library called react-day-picker. And the official Shadcn page lists its React 19 support as a "work in progress":
This issue may be resolved by the time you read this tutorial in the future, but for now, we will proceed with the native browser date picker.
Show Date in the Table
This one is easy. Just add a few lines in the Index.tsx
and import the library for date formatting.
resources/js/pages/Tasks/Index.tsx:
// ... import { TablePagination } from '@/components/table-pagination';import { format } from 'date-fns'; const breadcrumbs: BreadcrumbItem[] = [ // ... <TableRow> <TableHead>Task</TableHead> <TableHead className="w-[100px]">Status</TableHead> <TableHead className="w-[100px]">Due Date</TableHead> <TableHead className="w-[150px] text-right">Actions</TableHead></TableRow> // ... <TableRow key={task.id}> <TableCell>{task.name}</TableCell> <TableCell className={task.is_completed ? 'text-green-600' : 'text-red-700'}> {task.is_completed ? 'Completed' : 'In Progress'} </TableCell> <TableCell>{task.due_date ? format(task.due_date, 'PPP') : ''}</TableCell> <TableCell className="flex flex-row gap-x-2 text-right"> // ...
And here's the visual result:
Finally: The Edit Form
This one will also be easy, repeating the Create form logic and passing the due_date
where needed.
resources/js/pages/Tasks/Edit.tsx:
import { format } from 'date-fns'; // ... type EditTaskForm { name: string; is_completed: boolean; due_date?: string;} export default function Edit({ task }: { task: Task }) { const taskName = useRef < HTMLInputElement > (null); const { data, setData, errors, put, reset, processing } = useForm<Required<EditTaskForm>>({ name: task.name, is_completed: task.is_completed, due_date: task.due_date, }); // ... <form onSubmit={editTask} className="space-y-6"> // ... <div className="grid gap-2"> <Label htmlFor="name">Due Date</Label> <Input id="due_date" value={data.due_date ? format(data.due_date, 'yyyy-MM-dd') : ''} onChange={(e) => setData('due_date', format(new Date(e.target.value), 'yyyy-MM-dd'))} className="mt-1 block w-full" type="date" /> <InputError message={errors.due_date} /> </div> // ... </form>}
And the Edit form works, including passing the value from the database!
Here's the GitHub commit for the last two lessons: Pagination and Date Picker. (sorry, I forgot to commit separately)
How hard would it be to set the calende picker with the date and time aswell ?
If we go for native browser, maybe input type="datetime-local"?
I had problems with the
date-fns
library, incorrect format in the picker, I useddayjs
instead.Can you show us what you changed to get this to work? I added the npm install dayjs –save But what else do I have to do to get this to work?
date-fns worked for me without any problems.
In Create.tsx
Use the
format
functionWhat problems were you having?
Hi Povilas,
Something minor but important: In Create.tsx, the form is defined in the article as;
But it was actually created earlier as type and should be:
The same with EditTaskForm:
Hi, thanks for letting us know! I've updated the lesson to fix this :)
ps. This was a left-over update from following starter kit changes https://github.com/laravel/react-starter-kit/commit/bb97832a09926ab9c28b6bde87262fca4bc1800a#diff-6dcdf3ad723e9f82263160236fdb941e99915e8171a4f807b8a95a5c2aa89841R13
Under "Adding the Input Date to the Form" (For Create) , the following is missing:
This would set the
due_date
to today, but we intentionally left itnull
as we don't want to fill it with any default value.Or was there any other reason for this?
I meant the
due_date
property is missing which throws an errorBut it is there, no?
This is taken from the lesson and seems to be exactly what you are referring to
Ah! seen that it was previously set before that secton. No worries!
While the Edit form works with the addition of
due_date
, TypeScript is complaining, "Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'." I believe this is because we have defineddue_date
as optional in the type but by wrappingEditTaskForm
withRequired
, we are making all the properties ofEditTaskForm
required.I was able to fix the TypeScript error by making
due_date
optional as follows:I removed the Required from useForm and passed due_date or null