DEV Community

Dave Mackey
Dave Mackey

Posted on • Originally published at babyprogrammer.com on

Building Production-Grade Web Applications with Supabase – Part 2

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 | }
Enter fullscreen mode Exit fullscreen mode

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} />;
}
Enter fullscreen mode Exit fullscreen mode

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(
    ...
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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";
Enter fullscreen mode Exit fullscreen mode

And not like:

import TicketList from "./TicketList";
Enter fullscreen mode Exit fullscreen mode

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 | }
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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}">
Enter fullscreen mode Exit fullscreen mode

Is replaced with:

<article key={comment.date}>
Enter fullscreen mode Exit fullscreen mode

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",
  },
];
Enter fullscreen mode Exit fullscreen mode

Then we just need to change:

<article key={comment.date}>
Enter fullscreen mode Exit fullscreen mode

To:

<article key={comment.id}>
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

And we need to replace:

        <td>
         {user.name}
         ({user.isAvailable ? "Available" : "Not available"})
        </td>
Enter fullscreen mode Exit fullscreen mode

With:

<td style={{ color: !user.isAvailable ? "red" : undefined }}>
 {user.isAvailable ? <IconUserCheck /> : <IconUserOff />} {user.name}
</td>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)