Showing Chirps
In the previous step we added the ability to create Chirps, now we're ready to display them!
Retrieving the Chirps
Let's update the index
method of our ChirpController
class to pass Chirps from every user to our Index page:
<?php
// ...
class ChirpController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(): Response
{
return Inertia::render('Chirps/Index', [
//
'chirps' => Chirp::with('user:id,name')->latest()->get(),
]);
}
// ...
}
Here we've used Eloquent's with
method to eager-load every Chirp's associated user's ID and name. We've also used the latest
scope to return the records in reverse-chronological order.
INFO
Returning all Chirps at once won't scale in production. Take a look at Laravel's powerful pagination to improve performance.
Connecting users to Chirps
We've instructed Laravel to return the id
and name
property from the user
relationship so that we can display the name of the Chirp's author without returning other potentially sensitive information such as the author's email address. But, the Chirp's user
relationship hasn't been defined yet. To fix this, let's add a new "belongs to" relationship to our Chirp
model:
<?php
// ...
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Chirp extends Model
{
// ...
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
This relationship is the inverse of the "has many" relationship we created earlier on the User
model.
Updating our component
Next, let's create a Chirp
component for our front-end. This component will be responsible for displaying an individual Chirp:
<script>
let { chirp } = $props();
</script>
<div class="p-6 flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div class="flex-1">
<div class="flex justify-between items-center">
<div>
<span class="text-gray-800">{chirp.user.name}</span>
<small class="ml-2 text-sm text-gray-600">{ new Date(chirp.created_at).toLocaleString() }</small>
</div>
</div>
<p class="mt-4 text-lg text-gray-900">{chirp.message}</p>
</div>
</div>
Finally, we will update our Chirps/Index
page component to accept the chirps
prop and render the Chirps below our form using our new component:
<script>
import Chirp from '@/Components/Chirp.svelte';
import InputError from '@/Components/InputError.svelte';
import PrimaryButton from '@/Components/PrimaryButton.svelte';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.svelte';
import { page, useForm } from '@inertiajs/svelte';
const form = useForm({
message: ''
});
function submit(e) {
e.preventDefault();
$form.post(route('chirps.store'), {
onFinish: () => {
$form.reset();
}
});
}
const chirps = $page.props.chirps;
</script>
<svelte:head>
<title>Chirps</title>
</svelte:head>
<AuthenticatedLayout>
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
<form onsubmit={submit}>
<textarea required bind:value={$form.message} class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
placeholder="What's on your mind?"></textarea>
<InputError class="mt-2" message={$form.errors.message} />
<PrimaryButton class="mt-4">Chirp</PrimaryButton>
</form>
<div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
{#each chirps as chirp}
<Chirp {chirp} />
{/each}
</div>
</div>
</AuthenticatedLayout>
Now take a look in your browser to see the message you Chirped earlier!
STOP ✋
Before go to the next chapter, do you see something weird like we add the chirp but it doesn't appeared instantly in our chirp list? If we refresh the page then it appears. But why? 🧐
Here's the answer. 👀
Code in Svelte components is only executed once at creation. We need a $derived
rune as a reactive computation when dependencies change. In Chirps/Index.svelte
, the chirps
variable depends on $page.props.chirps
. The $page.props.chirps
is a dependency that will be change each time new chirp added. So, we use $derived
rune here.
const chirps = $page.props.chirps;
const chirps = $derived($page.props.chirps);
Extra Credit: Relative dates
In our Chirp
component we formatted the dates to be human-readable, but we can take that one step further by displaying relative dates using the popular Day.js library.
First, install the dayjs
NPM package:
npm install dayjs
Then we can use this library in our Chirp
component to display relative dates:
<script>
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
let { chirp } = $props();
</script>
<div class="p-6 flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div class="flex-1">
<div class="flex justify-between items-center">
<div>
<span class="text-gray-800">{chirp.user.name}</span>
<small class="ml-2 text-sm text-gray-600">{ new Date(chirp.created_at).toLocaleString() }</small>
<small class="ml-2 text-sm text-gray-600">{ dayjs(chirp.created_at).fromNow() }</small>
</div>
</div>
<p class="mt-4 text-lg text-gray-900">{chirp.message}</p>
</div>
</div>
Take a look in the browser to see your relative dates.