Courses

React Laravel 12 Starter Kit: CRUD Project

Due Date: Can't Use Shadcn Date Picker

Summary of this lesson:
- Add a `due_date` field to tasks with database integration
- Implement date input with input type="date" instead of Shadcn component
- Configure TypeScript types and date formatting with date-fns
- Display and edit dates in task listing and forms

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)

Previous: Table Pagination with React "Helper"
avatar

How hard would it be to set the calende picker with the date and time aswell ?

avatar

If we go for native browser, maybe input type="datetime-local"?

avatar
You can use Markdown
avatar
Luis Antonio Parrado

I had problems with the date-fns library, incorrect format in the picker, I used dayjs instead.

avatar

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?

avatar

date-fns worked for me without any problems.

npm install  date-fns --save

In Create.tsx

import { format } from 'date-fns'; 

Use the format function

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

What problems were you having?

avatar
You can use Markdown
avatar

Hi Povilas,

Something minor but important: In Create.tsx, the form is defined in the article as;

interface CreateTaskForm {
    name?: string,
    due_date?: string 
}

But it was actually created earlier as type and should be:

type CreateTaskForm = {
    name?: string;
   due_date?: string;
}

The same with EditTaskForm:

type EditTaskForm = {
    name: string;
    is_completed: boolean;
    due_date?: string;
};
avatar

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

avatar
You can use Markdown
avatar

Under "Adding the Input Date to the Form" (For Create) , the following is missing:

const { data, setData, errors, post, reset, processing } = useForm<Required<CreateTaskForm>>({ 
        name: '', 
        due_date: format(new Date(), 'yyyy-MM-dd'),   // This needs to be added
    }); 
avatar

This would set the due_date to today, but we intentionally left it null as we don't want to fill it with any default value.

Or was there any other reason for this?

avatar
You can use Markdown
avatar

I meant the due_date property is missing which throws an error

avatar

But it is there, no?

type CreateTaskForm {
    name?: string,
    due_date?: string 
}
 
export default function Create() {
    // ...
 
    const { data, setData, errors, post, reset, processing } = useForm<Required<CreateTaskForm>>({
        name: '',
        due_date: '', 
    });
}

This is taken from the lesson and seems to be exactly what you are referring to

avatar

Ah! seen that it was previously set before that secton. No worries!

avatar
You can use Markdown
avatar

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 defined due_date as optional in the type but by wrapping EditTaskForm with Required, we are making all the properties of EditTaskForm required.

I was able to fix the TypeScript error by making due_date optional as follows:

type EditTaskForm = {
    name: string;
    is_completed: boolean;
    due_date: string | null;
};

I removed the Required from useForm and passed due_date or null

const { data, setData, errors, put, reset, processing } = useForm<EditTaskForm>({
        name: task.name,
        is_completed: task.is_completed,
        due_date: task.due_date || null, 
    });
avatar
You can use Markdown
avatar
You can use Markdown