So let's now work
on that Meal Details page here.
For that I'll go to this meals folder
and then there this dynamic segment folder,
this mealSlug folder, and then there this page.js file
because that is the component responsible
for outputting the details about a meal.
Now here I again then want to output a fragment
because I want to have a header
and next to that the main section
as we had it on many other pages as well.
Now, I also prepared some styles for this page.
So attached you find a page.module.css file,
which you should store next to this page.js file.
And then we can import our classes
from that page.module.css file
so that then we can, for example, add a class here
to this header, a class named header.
In that header, I then want to have a couple of things.
For example, I wanna have a div in there
with a class of image, which will then be responsible
for outputting the meal image that belongs to a meal.
And we will output that image with help
of the image component,
which is provided by NextJS imported
from the next/image package.
Now we'll configure this image later
because at the moment we don't have the meal yet.
We'll add the logic for that soon though.
The only thing I want to add already is this fill prop here,
since again, I won't know the exact dimensions
of the image file yet,
and therefore, I'll use this fill prop as a fallback
or as a solution.
Then below that, I'll add another div
with a class of headerText,
written like this, not with a dash, but instead camel case.
That's how the CSS class name in the CSS file is named.
In there, you should then add a h1 element,
where we'll later output the meal title.
And below that a paragraph,
which receives another CSS class, a class of creator,
where I want to output the text by
and then the name of the creator wrapped into a link.
And here I'll use the regular anchor element
because this should actually be a link that allows us
to send an email to that creator.
Therefore, the actual href value
will be a dynamically generated string
using this string literal syntax
with those backticks here,
where I'll say mailto:
and then here I'll inject
that email address once we have it later.
And that's how we can set up a link
that will open the Mail program,
so that users can send an email to that person.
Below that, we can add another paragraph,
which also receives a class,
and here the class should be the summary class,
where I want to output the short summary text
that summarizes a meal.
And that's then this header.
Of course, a lot of data is missing,
but we'll take care of that soon.
Now in that main section,
I simply wanna output one paragraph,
which receives a CSS class name called instructions.
And then here, I want to output the instructions
that are stored for every meal.
And those should actually be output as HTML code,
which can be achieved in React
by targeting the dangerouslySetInnerHTML prop on an element.
And it's called like this because you open yourself up
to cross-site scripting attacks when outputting content
as HTML content, at least if you're not validating it.
Now this prop then wants a object as a value,
and that object should have an __html property,
which then contains the actual HTML code
that should be output on the screen.
Now we'll get that code later
from that meal once we fetched it,
but that's how we'll output it on the screen.
So that this is in the end our Meal Details page
without the data that we wanna output.
Now, in order to have some data
to output on that Meal Detail screen,
we should go back to the meals.js file in the lib folder,
and there we can export another async function
that could be called getMeal.
And here I then expect to get the slug of the meal
that identifies the meal that should be fetched.
Well, and then we simply wanna return db.prepare,
and then SELECT all columns FROM the meals table
WHERE the slug field
is equal to the slug we're getting here.
Now you could now add it like this,
but that would be insecure.
That opens yourself up to SQL injection.
Instead, you should use a question mark
as a placeholder here,
and then call the get method here
since I only want to get a single record,
and then you pass the value that should be inserted
for that placeholder to get.
In this case that's slug, which we get here.
Under the hood, this better-sqlite3 package we're using here
will then protect you against SQL injection attacks.
That's why you should add dynamic values
into your statements like this.
So that should then return a meal.
So we can therefore go back to this Meals Detail page here,
and now in there, call getMeal,
and import that from that lib folder,
and the meals.js file there.
Here, we'll then get back a meal,
which we can then use down there
to well populate this JSX code,
but getMeal of course needs that slug.
And that slug is simply that part that's encoded in the URL
that is caught with help of this placeholder here,
this mealSlug placeholder.
So how do we then get access to that concrete value
that's part of the URL?
With help of the props we are receiving on this page.
I mentioned before that NextJS is passing some special props
to these special files
or to these components in those special files.
And for example, every component
that's stored in a page.js file
will receive a special params prop,
which you can, therefore, destructure.
And this params prop will then itself contain an object
as a value where any dynamic path segment
that's configured for this route will be stored
as a key-value pair.
And this name you chose here
between those square brackets will be used as a key
and the actual value encoded in the URL will be used
as a value for that key.
And it is that value I need to get to look
for that value in the database.
So therefore, we can now pass params.mealsSlug to getMeal,
and that will be that identifier
that hopefully allows us to fetch a meal from the database.
Now to see whether that worked,
let's try outputting a title here with meal.title like this.
If we do that and save everything,
I don't get anything here.
Now the reason for that is that getMeal actually
returns a promise because I'm using this async keyword here
and I added that so that we could add such a delay again
if we wanted to
and if we wanted to set up a dedicated loading page
or handle loading in any other way.
Now I actually won't do that here,
and therefore, we can simply get rid of the async keyword,
therefore getMeal will no longer return a promise,
and therefore, now the code should work.
Indeed, if I now go back, I now see that title here,
which is now fetched from the database.
And of course that means
that we can now also output all the other data.
For example, on the image,
we can set the src prop here to meal.image
because we'll have such a image key in the database
and we're fetching that data now.
The alt text can be set to meal.title.
Here for the email address,
we can use meal.creator_email
because that's the name I'm using in a database.
You can see those names
if you take a look at this initdb.js file,
there, I have these field names
and that's what I'm now accessing here
in this Meal Detail page.
And the name can therefore be output by using meal.creator.
Now for the summary, it's meal.summary.
And for the HTML code down there, it's meal.instructions
without those curly braces though.
With all that done, if you now go back and reload,
you should see this meal page here.
Now you'll also see that this content down there
is not formatted perfectly.
And the reason for that is that the line breaks we got
in these instructions are ignored.
We can fix this by overriding meal.instructions
with meal.instructions.replace,
hence replacing parts of that string.
And here we can then use our regular expression
to look for all line breaks which are identified
by this special character.
Look for all of them in this string,
and by then replacing them with the br tag,
the line break tag.
If we do that, you see that the line breaks are back,
and that's now how we can load
and display those meal details.