I’m working through David Lorenz’s book Building Production-Grade Web Applications with Supabase (affiliate link) and just finished Chapter 3 – Creating the Ticket Management Pages…. I’ve run into a few issues and thought I’d share them along with how I’ve fixed them.
Section: Setting up Pico.css with Next.js
You can ignore pageProps.children
and leave it as children
.
Section: Building the login form
Should I really edit app/page.js as LoginPage?
Even though Lorenz explicitly states:
So, open up app/page.js and change the Page component so that it only will return the Login component for now…
I still had to go look for myself the next time I encountered instructions to edit LoginPage
. I expected us to create a new page rather than using the existing page.js
, but no, wipe everything out in page.js
and paste in only the LoginPage
code as given in the book.
Error: searchParams
should be awaited
Once we update the app/Login.js
with the toggling logic (for turning on/off the password field) we start seeing this error:
Error: Route "/" used `searchParams.magicLink`. `searchParams` should be awaited before using its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
at LoginPage (src/app/page.js:3:38)
1 | import { Login } from "./Login";
2 | export default function LoginPage({ searchParams }) {
> 3 | const wantsMagicLink = searchParams.magicLink === "yes";
| ^
4 | return <Login isPasswordLogin={!wantsMagicLink} />;
5 | }
To fix this we want to make the LoginPage
function in app/page.js
asynchronous like so:
import { Login } from "./Login";
export default async function LoginPage({ searchParams }) {
const params = await searchParams;
const wantsMagicLink = params.magicLink === "yes";
return <Login isPasswordLogin={!wantsMagicLink} />;
}
Saving the Username and Password
In the book we are instructed to update our code in Login.js
like so:
"use client"; import { useRef } from "react"; export const Login = () => { const emailInputRef = useRef(null); const passwordInputRef = useRef(null); return ( ... ) }
Just in case this isn’t entirely clear, here is what your code should look like:
"use client";
import { useRef } from "react";
import Link from "next/link";
export const Login = ({ isPasswordLogin }) => {
const emailInputRef = useRef(null);
const passwordInputRef = useRef(null);
return(
...
)
}
Where the ...
is we aren’t changing anything. Essentially, everything from return(
on remains the exact same as before.
The “big thing” I’m pointing out above is that one shouldn’t remove import Link from "next/link";
instead add "use client";
and the useRef
import before it.
Side note: Maybe we’ll learn later, but I find it a little odd to use useRef
instead of useState
here, but then again, I’m not a Next.js or React expert.
Where does that onSubmit
event go?
In Login.js
within the return( ... )
, replace the current <form>
with the form code that includes the onSubmit
handler.
Section: Visualizing the Ticket Management UI
Subsection: Creating a shared UI layout with navigation elements
The CSS ex unit?
In the code for app/tickets/TenantName.js
Lorenz uses the rarely used CSS ex
unit:
<strong style={{ marginLeft: "1ex" }}> {props.tenantName} </strong>
I suspect that this is actually a typo and that Lorenz intended for this to be 1em
. While ex
is a valid CSS unit it is rarely utilized and is based on the height of the current font. For more on this topic see:
- W3’s Example Page for EM, PX, PT, CM, IN…
- W3School’s CSS Units.
- Perplexity: Should one use the ex CSS unit?
Subsection: Designing the Ticket List Page
Creating dummy tickets where?
For those who aren’t familiar with React the instruction to add dummyTickets
to the page.js
file may not be clear enough. You’ll want put these in the TicketListPage()
function before the return
.
Import how?
Lorenz instructs us to import the TicketList
component into page.js
. This is pretty straightforward but may be helpful to note that since this is a “named export” we want our import in page.js
to look like:
import { TicketList } from "./TicketList";
And not like:
import TicketList from "./TicketList";
If you do the latter you’ll get one of those lovable error messages:
./src/app/tickets/page.js:1:1 Export default doesn't exist in target module 1 | import TicketList from "./TicketList"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | 3 | export default function TicketListPage() { 4 | const dummyTickets = [ The export default was not found in module [project]/src/app/tickets/TicketList.js [app-rsc] (ecmascript). Did you mean to import TicketList? All exports of the module are statically known (It doesn't have dynamic exports). So it's known statically that the requested export doesn't exist.
Subsection: Constructing the Ticket Details page
Error: Route “/tickets/details/[id]” used…
When we follow the instructions for creating the TicketDetailsPage
function we will see the following error:
Error: Route "/tickets/details/[id]" used `params.id`. `params` should be awaited before using its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
at TicketDetailsPage (src/app/tickets/details/[id]/page.js:4:50)
2 | return (
3 | <div>
> 4 | Ticket Details page with <strong>ID={params.id}</strong>
| ^
5 | </div>
6 | );
7 | }
You may recall that we’ve seen a similar error earlier with the LoginPage
function and that we resolved it by making our function async and awaiting the parameter. We’ll do the same here:
export default async function TicketDetailsPage({ params }) {
const ticketParams = await params;
const id = ticketParams.id;
return (
<div>
Ticket Details page with <strong>ID={id}</strong>
</div>
);
}
Error: Route “/tickets/details/[id]” used… (again?)
After updating the /tickets/details/[id]/page.js
file (TicketDetailsPage
function) we get a very similar error as we did in the last section. What gives? Simple, we updated our code in the last section but the book doesn’t know that, so the book is still using params.id
, simply replace params.id
with id
and everything should be right as rain.
Subsection: Adding the comments section to the ticket details
The path for the new comments file should be /tickets/details/[id]/TicketComments.js
and not /tickets/details[id]/TicketComments.js
.
Error: Encountered two children with the same key…
While Next.js doesn’t throw any errors in the terminal output after adding the code that displays the actual comments to TicketComments.js
you will see one in the browser:
Encountered two children with the same key, `{comment.date}`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
There are two reasons this occurs. The first is that we aren’t actually using the date as the key, because we have quotes around {comment.date}
we are passing in the string literal comment.date
. To fix this we need to remove the quotes so that this:
<article key="{comment.date}">
Is replaced with:
<article key={comment.date}>
Once this is done we won’t get that error anymore but we should note that there is another issue, even if it isn’t apparent at the moment. What happens if two or more individuals comment on the same date? Our keys again aren’t unique and we’ll see this same error. We can fix this quickly by adding an id
property to our comments. Our updated comments
should now look like:
const comments = [
{
id: 1,
author: "Dave",
date: "2027-01-01",
content: "This is a comment from Dave",
},
{
id: 2,
author: "Alice",
date: "2027-01-02",
content: "This is a comment from Alice",
},
];
Then we just need to change:
<article key={comment.date}>
To:
<article key={comment.id}>
Subsection: Implementing a page to create a new ticket
Nothing to see here folks.
Subsection: Implementing a user overview
Getting the Icons Installed
We need to install the @tabler/icons-react
package: npm i @tabler/icons-react
While Lorenz uses IconCheck
I’d recommend using IconUserCheck
as it’s a little clearer what one is displaying.
We need to import the IconUserCheck
and IconUserOff
components in our users/page.js
:
import { IconUserCheck, IconUserOff } from "@tabler/icons-react";
And we need to replace:
<td>
{user.name}
({user.isAvailable ? "Available" : "Not available"})
</td>
With:
<td style={{ color: !user.isAvailable ? "red" : undefined }}>
{user.isAvailable ? <IconUserCheck /> : <IconUserOff />} {user.name}
</td>
Subsection: Enhancing the navigation component
What’s that link look like again?
Here is what a link should look like when the pathname
code has been appended:
<Link role="button" href="/tickets" {...(pathname === "/tickets" ? activeProps : inactiveProps)}>Ticket List</Link>
Change the pathname === "/tickets"
to whichever page the link is pointing at, e.g. if the link points to /tickets/new
then you should set the pathname section to pathname === "/tickets/new"
.
The Conclusion
Congratulations, you are now person #3 that is interested in this post.
Top comments (0)