Notes on 3rd round of Next.js official tutorial

Last updated on 21 Sep. 2024

8 Sept. 2024: Starting 3rd round of Next.js official tutorial. First I am reading through its React tutorial (not trying out).

How To Create A Next.Js App With Serverless?, https://www.saffrontech.net/blog/how-to-create-a-nextjs-app-with-serverless, Jan. 2024 . Article explains serverless architecture in the context of Next.js. Around half-way down the article, how to create a serverless Next.js app is covered. Special steps are involved (e.g. installing 'serverless' framework and then running 'serverless create ...' command). I guess that means that the projects I have done so far in Next.js are NOT serverless including the official Next.js tutorial.

Chapter 4, Getting Started with React, https://nextjs.org/learn/react-foundations/getting-started-with-react has a nice simple example of using React with a CDN and also using Babel (JSX to JS compiler) with a CDN, and thus have only an HTML file that has React code which will run in a browser. npm etc. are not in the picture at all. This simple file index.html code:
<html>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Babel Script -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/jsx">
      const domNode = document.getElementById('app');
      const root = ReactDOM.createRoot(domNode);
      root.render(<h1>Develop. Preview. Ship.</h1>);
    </script>
  </body>
</html>
==================
The above code runs in the browser as expected though it takes some time to load.
Above React code is declarative as compared to equivalent JavaScript code which is imperative.

I checked my earlier Notes-post, https://raviswdev.blogspot.com/2024/04/notes-on-learning-nextjs.html and also some folders in my PC and confirmed that I had tried out some of the code in Chapters 9 and 10, though I seem to have only a modified version of Chatper 9 ("... confirms that the "use client" error that crops up for the same code when compiled in next.js, does not crop up in standard react without next").

Server Components, https://nextjs.org/docs/app/building-your-application/rendering/server-components gives lot of info. about rendering, streaming and hydration . From https://react.dev/reference/react-dom/client/hydrateRoot : "To hydrate your app, React will “attach” your components’ logic to the initial generated HTML from the server. Hydration turns the initial HTML snapshot from the server into a fully interactive app that runs in the browser."

Client Components, https://nextjs.org/docs/app/building-your-application/rendering/client-components is informative. ... Why do Client Components get SSR'd to HTML?, https://github.com/reactwg/server-components/discussions/4 [I think SSR in this context is Server Side Rendering.] ... "How are Client Components Rendered?" section is very useful.


Finished React Foundations part of tutorial.

Next step is to start with 'Learn Next.js' part of tutorial: https://nextjs.org/learn/dashboard-app 

----------------
13 Sep. 2024:



Finished Chapter 5: https://nextjs.org/learn/dashboard-app/navigating-between-pages ... Now plan to study app code of an early version (20240503-nextjs-dashboard) of the tutorial app. that I had created in earlier rounds.

"Use flex-shrink-0 to prevent a flex item from shrinking:" Ref: https://v2.tailwindcss.com/docs/flex-shrink has a nice example demonstrating it.
"Use flex-grow to allow a flex item to grow to fill any available space:" Ref: https://v2.tailwindcss.com/docs/flex-grow

"Use flex-none to prevent a flex item from growing or shrinking:", https://tailwindcss.com/docs/flex

"Use the overflow-auto utility to add scrollbars to an element in the event that its content overflows the bounds of that element. Unlike overflow-scroll, which always shows scrollbars, this utility will only show them if scrolling is necessary.", https://tailwindcss.com/docs/overflow ... "Use the overflow-y-auto utility to allow vertical scrolling if needed."

SideNav component, in md breakpoint, keeps Sign Out link at the bottom of the left panel by using an empty content div which is hidden in < md and becomes block in md with grow. Nav links are above this div and Sign Out is below in md. Also, in md, Nav links and Sign out are made flex-none. So the only flex item in this group that can grow is the empty content div which pushes Sign out to the bottom. h-screen and h-full are used in divs that enclose the main SideNav logo and links. That results in left panel in md taking up full height of window.

TypeScript Generics: What’s with the Angle Brackets <>?, https://javascript.plainenglish.io/typescript-generics-whats-with-the-angle-brackets-4e242c567269 ... Key points that I understood from this article:
* In function definition, what's passed between angle brackets is a 'generic' type
* In function invocation, a specific type is passed within angle brackets and function uses that type.


https://vercel.com/docs/storage/vercel-postgres/sdk - sql ... Construct SQL queries with the sql template literal tag.  ... Does not show angle brackets example but gives other sql template literal tag examples.

https://vercel.com/docs/storage/vercel-postgres/quickstart ... Intro to using Vercel Postgres SDK

In 'import { sql } from '@vercel/postgres';' sql is (as per VSCode intellisense)
(alias) const sql: VercelPool & (<O extends QueryResultRow>(strings: TemplateStringsArray, ...values: Primitive[]) => Promise<QueryResult<O>>)

In this usage, '    const data = await sql<Revenue>`SELECT * FROM revenue`;'
Revenue is:
(alias) type Revenue = {
    month: string;
    revenue: number;
}
----
and data is:
const data: QueryResult<Revenue>
----
So the returned data is of type QueryResult<Revenue>

Digging deeper into above sql type definition:
(alias) seems to be just an addition from VSCode Intellisense. In case of Revenue, the actual code (in lib/definitions.ts) is:
export type Revenue = {
  month: string;
  revenue: number;
};
----
The above usage is referred to as using a type alias (Revenue is the type alias here). See "Object Types", https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#object-types and "Type Aliases", https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases to see an example of using object type followed by same code but using a type alias.

The sql definition is in node_modules\@vercel\postgres\dist\index.d.ts and is as follows:
declare const sql: VercelPool & (<O extends QueryResultRow>(strings: TemplateStringsArray, ...values: Primitive[]) => Promise<QueryResult<O>>);
...
declare class VercelPool extends Pool {
    Client: typeof VercelClient;
    private connectionString;
    constructor(config: VercelPostgresPoolConfig);
    /**
     * A template literal tag providing safe, easy to use SQL parameterization.
     * Parameters are substituted using the underlying Postgres database, and so must follow
     * the rules of Postgres parameterization.
     * @example
     * ```ts
     * const pool = createPool();
     * const userId = 123;
     * const result = await pool.sql`SELECT * FROM users WHERE id = ${userId}`;
     * // Equivalent to: await pool.query('SELECT * FROM users WHERE id = $1', [id]);
     * ```
     * @returns A promise that resolves to the query result.
     */
    sql<O extends QueryResultRow>(strings: TemplateStringsArray, ...values: Primitive[]): Promise<QueryResult<O>>;
    connect(): Promise<VercelPoolClient>;
    connect(callback: (err: Error, client: VercelPoolClient, done: (release?: any) => void) => void): void;
}
...
From node_modules\@types\pg\index.d.ts :
export interface FieldDef {
    name: string;
    tableID: number;
    columnID: number;
    dataTypeID: number;
    dataTypeSize: number;
    dataTypeModifier: number;
    format: string;
}

export interface QueryResultBase {
    command: string;
    rowCount: number;
    oid: number;
    fields: FieldDef[];
}

export interface QueryResultRow {
    [column: string]: any;
}

export interface QueryResult<R extends QueryResultRow = any> extends QueryResultBase {
    rows: R[];
}
---------------------------------
====================



 & is used for intersection type which is a combination of multiple types with the resultant type having members of the all the types combined using intersection.

Figuring out details of sql above is turning out to be too complex. Not worther spending so much time on this, as of now. It is sufficient to know that the angle brackets after sql are associated with parameter type of some sort, and thus the result data from sql is probably typed to the type specified in the angle brackets. I have not been able to locate clear documentation that specifies this but this is what I have gathered from above definitions and reference pages so far.

In app\lib\data.ts fetchRevenue() method, I made the following test change:
    const data = await sql<Invoice>`SELECT * FROM revenue`;
    // const data = await sql<Revenue>`SELECT * FROM revenue`;
----
This, as expected, resulted in a TypeScript error indication (wavy red line) in app\dashboard\page.tsx in following code:
        <RevenueChart revenue={revenue} />
----
revenue outside braces has the wavy red line which expands to:
Type 'Invoice[]' is not assignable to type 'Revenue[]'.
  Type 'Invoice' is missing the following properties from type 'Revenue': month, revenue ts(2322)
revenue-chart.tsx(15, 3): The expected type comes from property 'revenue' which is declared here on type 'IntrinsicAttributes & { revenue: Revenue[]; }'
(property) revenue: Revenue[]"
----

I later undid the test changes (and the TS wavy line error indicator went away).

==============
How to understand the definition of a const 'sql' in vercel/postgres package, https://stackoverflow.com/questions/78342254/how-to-understand-the-definition-of-a-const-sql-in-vercel-postgres-package - does not give a proper answer but the issue itself is covered and has some related Vercel types & interfaces info. which may be the same as given above in this notes-post.

==============

app\ui\dashboard\revenue-chart.tsx is an interesting bar chart rendering component. Was able to figure out most of its working (mainly the styling is complex) without too much effort. But I don't think I should go in for such charts myself - it is better to simply explore using free chart components.

===========================
Finished study of main/uncommented part of app code of an early version (20240503-nextjs-dashboard) of the tutorial app. that I had created in earlier rounds.

=================

The current tutorial page for chapter 6, Setting Up Your Database, refers to a seed folder and route.ts file and going to 'localhost:3000/seed' to seed the database. The code I have from the tutorial of some months back has a seed.js file which seems to be a console based program to seed the database. I could not find a way to access the older tutorial page.

My earlier round(s) notes, https://raviswdev.blogspot.com/2024/04/notes-on-learning-nextjs.html , mentions not seeing date probably in Vercel postgres Storage part of Nextjs project page on Vercel - "Seeding data seemed to have only one issue of date column not being created in invoices. " But I later noted that the app itself was showing dates or working when dates were involved.

Now in this round when I see the data on Vercel postgres Storage page in Nextjs project page, I can see the date column entries for invoices! Maybe I missed something in my earlier round(s).
==============

====================================

14 Sep. 2024

Have started going through the final nextjs-dashboard project that I had done in earlier round(s). I am doing this in the context of Next.js tutorial chapters 7 to 9 (Fetching Data, 'Static and Dynamic Rendering' and Streaming).

Some parts of app\ui\skeletons.tsx which seems to have all the skeletons used in the app (both at loading.tsx level and at individual components wrapped within Suspense elements and having fallback as component skeletons) are quite complex Tailwind CSS. I felt it is not justified to spend time to understand the complex CSS part and the file in general as I am not sure if I will have to use such a skeleton done manually.

app\ui\dashboard\cards.tsx has interesting use of object properties being accessed with the bracket notation and the value within the brackets being a variable which has an object property name. Related code:

import {
  BanknotesIcon,
  ClockIcon,
  UserGroupIcon,
  InboxIcon,
} from '@heroicons/react/24/outline';
...
const iconMap = {
  collected: BanknotesIcon,
  customers: UserGroupIcon,
  pending: ClockIcon,
  invoices: InboxIcon,
};
...
      <Card title="Collected" value={totalPaidInvoices} type="collected" />
      <Card title="Pending" value={totalPendingInvoices} type="pending" />

...

export function Card({
  title,
  value,
  type,
}: {
  title: string;
  value: number | string;
  type: 'invoices' | 'customers' | 'pending' | 'collected';
}) {
  const Icon = iconMap[type];
------------

Another noteworthy point in above code is the TS spec. for type being a union of valid string values rather than just string.
-------------

The dashboard skeleton uses same skeletons (e.g. RevenueChartSkeleton) that are used as fallback for Suspense elements in dashboard page. Don't know if that is (very slightly) inefficient and whether these component skeletons get re-rendered when dashboard page is first loaded. Visually, I could not make out any rerendering of these skeletons. The streaming works effectively and the Revenue chart which has a delay of few seconds, gets rendered (final component rendering overwriting skeleton) after some other components are rendered, demonstrating the utility of Suspense elements at component within Page level as against using only loading.tsx for streaming.

----------

app\lib\data.ts code uses nullish coalescing operator ??:
const numberOfInvoices = Number(data[0].rows[0].count ?? '0');
Am postponing study of code in data.ts that comes into play after Chapter 9.

------------------
The grid size control of Revenue Chart + Latest Invoices, for various media breakpoints is a little complex (at least, to me). Relevant code:
From app\dashboard\(overview)\page.tsx :
      <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
        <Suspense fallback={<RevenueChartSkeleton />}>
          <RevenueChart />
        </Suspense>
        {/* <RevenueChart revenue={revenue} /> */}
        <Suspense fallback={<LatestInvoicesSkeleton />}>
          <LatestInvoices />
        </Suspense>
        {/* <LatestInvoices latestInvoices={latestInvoices} /> */}
      </div>
------------

From app\ui\dashboard\revenue-chart.tsx :
    <div className="w-full md:col-span-4">
      <h2 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
        Recent Revenue
...
--------

From app\ui\dashboard\latest-invoices.tsx :
    <div className="flex w-full flex-col md:col-span-4">
      <h2 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
        Latest Invoices
---------

The grid is defined at dashboard page level. Till md breakpoint it has only one column, and correspondingly till md breakpoint, RevenueChart and LatestInvoices do not specify a col-span. Straight forward code, so far.
At md breakpoint, grid at dashboard page level has 4 columns per row. But at md breakpoint both RevenueChart and LatestInvoices have a col-span-4 (each column with span 4 column widths), so effectively, at md breakpoint, RevenueChart and LatestInvoices each take up the whole grid row, and so are shown one below the other, as earlier. Note that at md breakpoint, the links part of the SideNav bar move from the top to the side (flex-col). So the width for the dashboard page reduces at md breakpoint.
At lg breakpoint, grid at dashboard page level has 8 columns per row with no change in col-span-4 for RevenueChart and LatestInvoices. So from lg breakpoint, half of the width is taken up Revenue Chart and other half by LatestInvoices.
============

Promise.all is the key to parallel data fetching in the tutorial.

The tutorial says that CardWrapper component prevents popping but I am not clear whether that would be the case if CardWrapper component were not used, as the underlying data fetch for the cards is in a Promise.all.

Read chapter 10 but did not go in depth as it is experimental stuff.

https://tailwindcss.com/docs/flex - Use flex-1 to allow a flex item to grow and shrink as needed, ignoring its initial size

'flex-1 flex-shrink-0' is used in app\ui\search.tsx . flex-shrink-0 may be cancelling 'shrink as needed' ... But is there not a simple alternative? How about flex-grow?

https://tailwindcss.com/docs/screen-readers : "Use sr-only to hide an element visually without hiding it from screen readers:" 

The Search input field uses a combination of pl-10 (large left padding) for the input field with a Magnifying Glass icon being positioned 'absolute left-3 top-1/2' with enclosing div having position relative. That seems to result in  the icon being rendered on the left and the search input field to its right.

https://www.w3schools.com/css/tryit.asp?filename=trycss_form_icon provides a simpler solution though Next.js tutorial app may have some additional polish.

peer and peer-focus are used in app. From 'Styling based on sibling state (peer-{modifier})', https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state : "When you need to style an element based on the state of a sibling element, mark the sibling with the peer class, and use peer-* modifiers like peer-invalid to style the target element".

But it seems that the peer and peer-focus are not having any impact in the app. Tried out some variations like in Tailwind doc above, but failed to have any impact.

============================

15 Sep. 2024

https://www.postgresql.org/docs/current/functions-matching.html : "The key word ILIKE can be used instead of LIKE to make the match case-insensitive according to the active locale. This is not in the SQL standard but is a PostgreSQL extension."


Related code from the tut app (file: app\lib\data.ts):
      SELECT
        invoices.id,
        invoices.amount,
        invoices.date,
        invoices.status,
        customers.name,
        customers.email,
        customers.image_url
      FROM invoices
      JOIN customers ON invoices.customer_id = customers.id
      WHERE
        customers.name ILIKE ${`%${query}%`} OR
        customers.email ILIKE ${`%${query}%`} OR
        invoices.amount::text ILIKE ${`%${query}%`} OR
        invoices.date::text ILIKE ${`%${query}%`} OR
        invoices.status ILIKE ${`%${query}%`}
      ORDER BY invoices.date DESC
      LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
    `;
-------------
Related code from scripts\seed.js

    const createTable = await client.sql`
    CREATE TABLE IF NOT EXISTS invoices (
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    customer_id UUID NOT NULL,
    amount INT NOT NULL,
    status VARCHAR(255) NOT NULL,
    date DATE NOT NULL
  );
`;
-------

Data type of amount is INT and date is DATE. So they have to be cast to text before ILIKE operator.
==========================

export const formatCurrency = (amount: number) => {
  return (amount / 100).toLocaleString('en-US', {
    style: 'currency',
    currency: 'USD',
  });
};
-----

Intl, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl - "Unlike most global objects, Intl is not a constructor. You cannot use it with the new operator or invoke the Intl object as a function. All properties and methods of Intl are static (just like the Math object)."



----
export const formatDateToLocal = (
  dateStr: string,
  locale: string = 'en-US',
) => {
  const date = new Date(dateStr);
  const options: Intl.DateTimeFormatOptions = {
    day: 'numeric',
    month: 'short',
    year: 'numeric',
  };
  const formatter = new Intl.DateTimeFormat(locale, options);
  return formatter.format(date);
};
----


========================

app\ui\invoices\table.tsx has:
    <div className="mt-6 flow-root">
      <div className="inline-block min-w-full align-middle">

---
On the net, articles about flow-root talk about using it to avoid some problems that float creates. But I could not find any matches for 'float' in the above page. So perhaps the page code uses it for something else.

https://developer.mozilla.org/en-US/docs/Web/CSS/display#flow-root states, "flow-root" .. "The element generates a block box that establishes a new block formatting context, defining where the formatting root lies."

Block formatting context, https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Block_formatting_context states, "A block formatting context (BFC) is a part of a visual CSS rendering of a web page. It's the region in which the layout of block boxes occurs and in which floats interact with other elements." It goes on to give some cases that create a BFC. Floats are only one such case. Other cases include absolutely positioned elements, inline-blocks, table cells etc. The above tutorial page code uses inline-block. It also has <td> elements which may imply usage of 'table cell' (), as above MDN page states, "Table cells (elements with display: table-cell, which is the default for HTML table cells)."

Does not address above issues but explains block and inline layouts very well: Block and inline layout in normal flow, https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flow_layout/Block_and_inline_layout_in_normal_flow

inline-block respects width and height whereas inline does not. Perhaps that's why above table.tsx code uses inline-block (and min-w-full)

I don't think I have used 'display: table' and related CSS, so far. So the Tailwind CSS associated with table in app\ui\invoices\table.tsx is quite new to me. The question is how much time I must invest in studying it now. Perhaps a good approach would be to do a quick overview study and postpone a deeper study to when I have the need for better understanding of this code, say for some software development work.

I could not get in-depth articles on it. Here are two articles that cover the display: table related CSS but they do not seem to be so relevant to this tutorial app code (table.tsx):

Some complex Tailwind CSS in table.tsx:
            <tbody className="bg-white">
              {invoices?.map((invoice) => (
                <tr
                  key={invoice.id}
                  className="w-full border-b py-3 text-sm last-of-type:border-none [&:first-child>td:first-child]:rounded-tl-lg [&:first-child>td:last-child]:rounded-tr-lg [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg"
                >
                  <td className="whitespace-nowrap py-3 pl-6 pr-3">
-----

Nested Brackets and Ampersand usage in Tailwind UI examples, https://stackoverflow.com/questions/73666015/nested-brackets-and-ampersand-usage-in-tailwind-ui-examples led me to https://tailwindcss.com/docs/hover-focus-and-other-states which uses & . From it: "You can create one-off group-* modifiers on the fly by providing your own selector as an arbitrary value between square brackets:" and "For more control, you can use the & character to mark where .group should end up in the final selector relative to the selector you are passing in:"

So I think I first need to read up about groups in Tailwind (in same page).
From: Styling based on parent state (group-{modifier}), https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state 
"When you need to style an element based on the state of some parent element, mark the parent with the group class, and use group-* modifiers like group-hover to style the target element:" .. I think I had gone through this section in some other context. The examples are fairly easy to understand.

Seems like the key section for table.tsx page code above, is: Using arbitrary variants, https://tailwindcss.com/docs/hover-focus-and-other-states#using-arbitrary-variants .

Took me quite a bit of searching to get to & MDN reference page: & nesting selector, https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector . The section "Examples" (internal link broken) in it gives a simple example of using &:
CSS without & (nesting selector):
.example {
  font-family: system-ui;
  font-size: 1.2rem;
}

.example > a {
  color: tomato;
}

.example > a:hover,
.example > a:focus {
  color: ivory;
  background-color: tomato;
}
-----
Equivalent CSS with & (nesting selector):
.example {
  font-family: system-ui;
  font-size: 1.2rem;
  & > a {
    color: tomato;
    &:hover,
    &:focus {
      color: ivory;
      background-color: tomato;
    }
  }
}
----


Getting back to Tailwind code now, I decided to read up on square bracket selector in CSS as I don't think I have used it or studied about it in my non Tailwind CSS code. But later I realized that square bracket in Tailwind seems to not correspond to square bracket in CSS. But anyway, some little info. about square bracket in CSS is given below:


An example usage is: 
"[attr] a[title] Matches elements with an attr attribute (whose name is the value in square brackets)."
My understanding of above is that a[title] will match a (anchor) elements with title attribute.
[Seems like any HTML element including anchor tag can have title attribute, https://www.w3schools.com/tags/att_global_title.asp .]

There are other variations of attribute selectors like "[attr=value]".


Example HTML:
<ul role="list">
  {#each items as item}
    <li class="[&:nth-child(3)]:underline">{item}</li>
  {/each}
</ul>
----
Example Generated CSS:
/* https://media.giphy.com/media/uPnKU86sFa2fm/giphy.gif */
.\[\&\:nth-child\(3\)\]\:underline:nth-child(3) {
  text-decoration-style: underline
}
----
The '.\[\&\:nth-child\(3\)\]\:underline' part of generated CSS seems to be just the Tailwind class name with some characters escaped. The main CSS code for this class is:
nth-child(3) {
  text-decoration-style: underline
}

But can't we simply use "nth-child(3):underline" as the Tailwind class? It seems that that will not work in Tailwind. There are TW utility classes for first and last child, odd and even child but not for nth-child(x) - it seems.
An interesting article which seems to be in line with my above statements: Tailwind CSS Nth-Child Selector Cheat Sheet, https://cruip.com/tailwind-css-nth-child-selector-cheat-sheet/#nth-selector-11

Another example from above using arbitrary variants TW documentation page:
"If you need spaces in your selector, you can use an underscore."
<div class="[&_p]:mt-4">

-----

All p elements within the div will be selected and have margin-top as 1 rem.
==============

Now the above table.tsx code can be understood. In :
[&:first-child>td:first-child]:rounded-tl-lg [&:first-child>td:last-child]:rounded-tr-lg [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg
---
In the first class spec. above, tr element's first child of first row is given rounded-tl-lg TW class.
For it, VSCode Intellisense shows:
.\[\&\:first-child\>td\:first-child\]\:rounded-tl-lg:first-child>td:first-child {
    border-top-left-radius: 0.5rem /* 8px */;
}
----
Replacing the complex TW class name with simply classX, that essentially is CSS of :
.classX:first-child>td:first-child {
    border-top-left-radius: 0.5rem /* 8px */;
}
This is assigned to tr element. So tr element's first child (first row) and its first td child element (first row, first column cell) is selected and given - border-top-left-radius: 0.5rem .
The other class specs. above can also be understood on similar lines.
============

16 Sept. 2024

In VSCode, for plain HTML files using Tailwind via CDN, Tailwind Intellisense was not showing even though the related extension had been installed.
The solution is to create an empty file named tailwind.config.js in the directory having the HTML file. Strange fix but I tried it and it works. Ref: Tailwind Auto complete is not working in plan HTML CSS JS project VS Code, https://www.youtube.com/watch?v=HGtz7P9xtmg , 4 mins.
---

I created some test table html files which try to replicate the table Tailwind CSS in next.js tutorial app, to try out changes to table Tailwind CSS for better understanding. tablev1.html recreates 3 rows of next.js tutorial app invoices. I made minor changes in background and border colour to make them more visible. In next.js app I could switch on dark mode using the Chrome extension I have, to check table border related stuff but tablev1.html does not change on dark mode (perhaps next.js app has some dark mode settings at higher layout.tsx file(s) level).
----------

Removing 'last-of-type:border-none' from class of last tr entry results in border being shown below last row, as expected. Note that border width is specified only for border bottom (and so other borders will not appear anyway).

Is [&:first-child>td:first-child] needed? Can it not simply be: first-child>td:first-child ?
Intellisense does not show anything for 'first-child>td:first-child:rounded-tl-lg' when applied to first tr, or when applied to tbody. So looks like that is not valid Tailwind code.

Tailwind Intellisense does not show anything while typing out the contents of following within square brackets:
[&:first-child>td:first-child]:rounded-tl-lg
It shows up only for the rounded-tl-lg part, after which it applies that to the whole specification including the square brackets (if I recall correctly).

https://tailwindcss.com/docs/hover-focus-and-other-states states that Tailwind has "Pseudo-classes, like :hover, :focus, :first-child, and :required". So first-child should be recognized by Intellisense when it is typed in by itself. But it is not!

Figured out that first is the keyword for first-child. So 'first:border-b-2' is recognized by Intellisense and shows the CSS for it as:
.first\:border-b-2:first-child {
    border-bottom-width: 2px;
}
--------------
It also results in the first row having thicker border, even if it is specified for all three rows, as expected.

'first:>td:first:rounded-tl-lg' in tbody is not recognized though while typing, first: part of it got recognized (by Intellisense)

Moving 'first:border-b-2' from 1st tr to tbody does not work even though it is recognized as earlier by Tailwind Intellisense. The reason for that is given below.

https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child states, "The :first-child CSS pseudo-class represents the first element among a group of sibling elements." So it can be used only at the tr (3 siblings) level or td (more siblings) level, and not at tbody level.

first:>td:first-child:rounded-tl-lg in 1st tr row does not work.
first:>td:first:rounded-tl-lg in 1st tr row also does not work.

So looks like using the > selector (child selector) in above cases needs the square bracket and & usage.

Tried '[first-child>td:last-child]:rounded-tr-lg' (removing &:) in 1st tr row. Intellisense did not recognize it and the top right edge of the table body became square instead of rounded.

https://tailwindcss.com/docs/hover-focus-and-other-states#using-arbitrary-variants states, "Just like arbitrary values let you use custom values with your utility classes, arbitrary variants let you write custom selector modifiers directly in your HTML." .. "Arbitrary variants are just format strings that represent the selector, wrapped in square brackets."

CSS related jargon is still not very clear to me. But given above experimentation, I think the above means that whatever comes after '[&:'   till ']' is escaping into regular CSS.
'[&:first-child>td:first-child]:rounded-tl-lg' is expanded by Intellisense to:
.\[\&\:first-child\>td\:first-child\]\:rounded-tl-lg:first-child>td:first-child {
    border-top-left-radius: 0.5rem /* 8px */;
}
---
Note that the CSS does not have & . I think the & in Tailwind CSS does NOT map to & (nesting selector) in CSS.

----
[https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values gives an 'arbitrary value' example as '<div class="top-[117px]">', I have used such arbitrary values in app code. What put me off in this 'arbitrary variants' case is the &:]

Replacing the complex class name by simply classX, we have:
.classX:first-child>td:first-child {
    border-top-left-radius: 0.5rem /* 8px */;
}
----

I made following changes to the file:
    <style>
      .classX:first-child > td:first-child {
        border-top-left-radius: 0.5rem;
      }
    </style>
---

Then I modified the first row class specification as:
              <tr
                class="w-full border-b border-black py-3 text-sm last-of-type:border-none classX [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg"
              >
----
The top left of the table body got rounded. As expected, top right was not rounded and bottom left and bottom right were both rounded.
tablev2.html has the code.
---------

I wondered whether I could use the .classX style specification as an inline style. I could not get first-child to be recognized as an inline style but could get simple stuff like 'color:blue' to get recognized in Intellisense and show up in the browser. tablev3.html has the code.
As per: How to add nth-child() style in inline styling?, https://stackoverflow.com/questions/25646683/how-to-add-nth-child-style-in-inline-styling and CSS Pseudo-classes with inline styles, https://stackoverflow.com/questions/5293280/css-pseudo-classes-with-inline-styles , "an inline style attribute can only contain property declarations" and you cannot use selectors, pseudo-classes/pseudo-elements etc.
================

18 Sept. 2024

Header for col or row or ...
No effect in ordinary web browsers but can be read by screen readers.
---

Invoices table seems to have a small issue when the width and height of the window are reduced but not to the point where the sidenav moves from left to top. Long name (Delba de Oliveira) end gets on top of email beginning part.
Width of table cells/columns is not specified. The width of the columns for max window width seems to be a little arbitrary.
What is the default width of an HTML table cell <td>?, https://stackoverflow.com/questions/31926939/what-is-the-default-width-of-an-html-table-cell-td - one response states that it is up to the browser to decide.

=========================

What is this underscore: [...Array(5)].map((_,x) => x++);, https://www.reddit.com/r/javascript/comments/8842um/what_is_this_underscore_array5map_x_x/ : Comments explain that _ is used to denote an unused variable. Instead of _, 'unused' could be used as variable name.

Syntax for one usage: Array.from(arrayLike, mapFn)
First argument can be: "array-like objects (objects with a length property and indexed elements)."


Examples of Array-like objects: 
The arguments object, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments : "arguments is an array-like object accessible inside functions that contains the values of the arguments passed to that function."

Why do you need to know about Array-like Objects?, https://daily.dev/blog/why-do-you-need-to-know-about-array-like-objects

Calling map() on non-array objects, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#calling_map_on_non-array_objects : "The map() method reads the length property of this and then accesses each property whose key is a nonnegative integer less than length."


Tutorial app uses _ and Array.from on array-like object, in this code in app\lib\utils.ts:
return Array.from({ length: totalPages }, (_, i) => i + 1);
----
I think Array.from may be passing undefined as _ variable value for each invocation.
For totalPages = 5, return value would be [1,2,3,4,5]. 

-----
Pagination component in app\ui\invoices\pagination.tsx uses following code:
      <div className="inline-flex">
        <PaginationArrow
          direction="left"
          href={createPageURL(currentPage - 1)}
          isDisabled={currentPage <= 1}
        />
...
----
Why use inline-flex here? It is contained in a div which is a normal flex and there are no elements before or after Pagination component. The code using Pagination in app\dashboard\invoices\page.tsx:
      <div className="mt-5 flex w-full justify-center">
        <Pagination totalPages={totalPages} />
      </div>
----

I modified the code to use flex instead of inline-flex as follows, and it seems to work properly.
      <div className="flex">
        <PaginationArrow
          direction="left"
          href={createPageURL(currentPage - 1)}
          isDisabled={currentPage <= 1}
        />
...
----

Perhaps the reason may be that code using Pagination may have inline elements before or after Pagination even if in this case they don't.

Example of inline-flex: Section 'Inline-flex' in What is the difference between inline-flex and inline-block in CSS?, https://www.geeksforgeeks.org/what-is-the-difference-between-inline-flex-and-inline-block-in-css/
---

Typescript seems to have a nice way to handle enum kind of variables/arguments. From app\ui\invoices\pagination.tsx :
function PaginationArrow({
  href,
  direction,
  isDisabled,
}: {
  href: string;
  direction: 'left' | 'right';
  isDisabled?: boolean;
}) {
----
So direction here is like an enum which can have 'left' or 'right' values only.

TS also has support for regular enums: https://www.typescriptlang.org/docs/handbook/enums.html

I think now I have completed detailed study of code related to Chapter 11.
========================

How to make a placeholder for a ‘select’ box?, https://www.geeksforgeeks.org/how-to-make-a-placeholder-for-a-select-box/ - Select tag does not seem to have a placeholder attribute.

app\ui\invoices\create-form.tsx uses following code for placeholder in select:
              <option value="" disabled>
                Select a customer
              </option>
----

From https://developer.mozilla.org/en-US/docs/Web/CSS/::placeholder : "The ::placeholder CSS pseudo-element represents the placeholder text in an <input> or <textarea> element."

The tut app code uses ::placeholder for select element as well! I think that code gets ignored as when I changed it to "placeholder:text-red-900" there was no impact on the page display. The placeholder entry is marked as disabled which seems to result in lighter colour for it in the drop-down but the placeholder text (different from placeholder entry in drop down list) seems to be same colour as not-disabled entries in drop-down.

The ::placeholder CSS pseudo-element is used for Amount input field where it seems to be having an as-expected effect.

Invoice Status radio buttons of Pending and Paid, are in a fieldset with a legend. fieldset is not used elsewhere in the form.

After filling up create invoice form, instead of pressing 'Create Invoice' button, navigating away (without saving) to SideNav bar links like Customers, does not show any warning about unsaved data, and the user is shown the page he navigated to. Similarly no warning shown when navigating to links outside the app without saving.

In:
                <input
                  id="paid"
                  name="status"
                  type="radio"
                  value="paid"
                  className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
                />
---
focus:ring-2 seems to be the default as removing it does not make any impact.
Also the colour of the ring is not specified (blue is shown which may be the default).
Changing code in the earlier radio button CSS to:
focus:ring-4 focus:ring-red-900
---
had the expected impact of thicker red coloured ring on focus.
=======

https://nextjs.org/docs/app/api-reference/file-conventions/page explains params and searchparams props of Next.js Page.
Params is for getting dynamic route parameter.
Searchparams is for getting query string data.

=======================

Round 2 has some unanswered questions just above the sentence, "That finishes the notes for the tutorial page, "Adding Search and Pagination"." I think I should revisit those questions later on.

=============

While in an earlier round, I had noted how function.bind ('const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);') was used to pass invoice.id as an argument to the updateInvoice form action, along with formData which is provided by default, I think this technique slipped from my mind when I was developing my Gita web app. I don't clearly recall the scenario now but I think I wanted to pass some additional argument to a form action besides the formData but could not figure out how to do that.

Hopefully me putting up this note will drum this technique deeper into my head and I will remember it next time I need to do something similar.
==============

21 Sept. 2024

app\dashboard\invoices\error.tsx has:

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
---

In the above, 'error: Error & { digest?: string };' is a little obscure. VSCode Intellisense informs us that this Error (different from function Error) is an interface. What does type Error correspond to? VSCode Intellisense Go to Definition provides two definitions which are not so easy to understand. 

https://nextjs.org/learn/dashboard-app/error-handling states about the error prop, "error: This object is an instance of JavaScript's native Error object."

Error, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error is easier to understand. VSCode lists cause, digest, message, name and stack as the properties of the interface/type of error prop. passed to Error component. The above MDN page for JavaScript Error lists cause, message, name and stack as properties of the Error object. Next.js code above adds digest as a property of the interface/type of the error prop.


===========================

In app\ui\invoices\create-form.tsx the form code has 'outline-2' for a few elements like <input> for amount. But I could not find outline (style) specification due to which I think the outline-2 is ignored. I confirmed it by changing outline-2 to outline-8 and there was no change in the form display. But then I added outline (default outline solid style) and now the outline was reflecting the outline-8 class. The colour was black instead of blue.

It seems that the blue outline that appears on the data-input fields of this form on tabbing through them, is the Tailwind and/or browser default. [So, it seems that, the developer need not code for this.]

=========================

Awesome tutorial that covers lot of ground and is at a good pace: React Hook Form - Complete Tutorial (with Zod), https://www.youtube.com/watch?v=cc_xmawJ8Kg, 28 mins, Jan. 2024 by Cosden Solutions. ... Code: https://github.com/cosdensolutions/code/tree/master/videos/long/react-hook-form-tutorial .. main file: https://github.com/cosdensolutions/code/blob/master/videos/long/react-hook-form-tutorial/src/App.tsx

React Hook Form - Get Started, https://react-hook-form.com/get-started
React Hook Form - Get Started, https://www.youtube.com/watch?v=RkXv4AXXC_4, 8 mins, Dec. 2021

Form validation without libraries like react-hook-form:
Form Validation using React | React Forms Handling & Validation Tutorial | React Sign up Form, https://www.youtube.com/watch?v=EYpdEYK25Dc , Dipesh Malvia, 21 mins, Oct. 2021 ... Code repo: https://github.com/dmalvia/React_Forms_Tutorials/tree/use-native .. main file: https://github.com/dmalvia/React_Forms_Tutorials/blob/use-native/src/App.js .. Note that 'Semantic UI' (CSS class library) is used - https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css (in public/index.html).

MDN HTML & JS stuff: Client-side form validation, https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation

========================
Easy paced video about React hooks but there may be some inaccuracies: Learn React JS Hooks | React Hooks Tutorial | React Hooks Explained | React Hooks for Beginners, https://www.youtube.com/watch?v=hJ5UEtdS8qE

https://react.dev/reference/react/hooks is good and accurate but is a more intense read.

Differences between refs and state, https://react.dev/learn/referencing-values-with-refs#differences-between-refs-and-state : Just above the section, "When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn’t require a re-render, using a ref may be more efficient." At the beginning of the section, "But in most cases, you’ll want to use state. Refs are an “escape hatch” you won’t need often."
Refs can be used for: "Storing timeout IDs", "Storing and manipulating DOM elements ...", "Storing other objects that aren’t necessary to calculate the JSX".

================

Chapter 14, "Improving Accessability", section "Client-Side validation", https://nextjs.org/learn/dashboard-app/improving-accessibility#client-side-validation states, "There are a couple of ways you can validate forms on the client." But it covers only one way which is using form validation supported by browser (e.g., required). It does not cover using libraries like React hook form and/or Zod.

[Seems to be an interesting article on first quick-browse:] Zod and React: A Perfect Match for Robust Validation, https://www.dhiwise.com/post/zod-and-react-a-perfect-match-for-robust-validation, LU Aug 2024.

================

https://zod.dev/?id=basic-usage explains difference between parse and safeParse methods with a simple example copy-pasted below:

import { z } from "zod";

// creating a schema for strings
const mySchema = z.string();

// parsing
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // => throws ZodError

// "safe" parsing (doesn't throw error if validation fails)
mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }
----------

The documentation for various primitives and also other documentation seems good. For example, https://zod.dev/?id=strings  :
'z.string().email({ message: "Invalid email address" });' shows how useful zod can be.
====================
https://zod.dev/?id=basic-usage shows how to customize some common error messages for string schema:
const name = z.string({
  required_error: "Name is required",
  invalid_type_error: "Name must be a string",
});

Next.js tutorial code: In app\lib\actions.ts :
const FormSchema = z.object({
  id: z.string(),
  customerId: z.string({
    invalid_type_error: 'Please select a customer.',
  }),
  amount: z.coerce
    .number()
    .gt(0, { message: 'Please enter an amount greater than $0.' }),
  status: z.enum(['pending', 'paid'], {
    invalid_type_error: 'Please select an invoice status.',
  }),
  date: z.string(),
});
----
From the same file:
const CreateInvoice = FormSchema.omit({ id: true, date: true });
const UpdateInvoice = FormSchema.omit({ id: true, date: true });
----
Don't know what role 'true' plays. The documentation has similar examples but does not explain the 'true' part.
Why can't a single object schema be used for both CreateInvoice and UpdateInvoice? Note that they are constants.

================
When I was going through initial rounds of next.js tutorial starting in late April/early May 2024, https://raviswdev.blogspot.com/2024/04/notes-on-learning-nextjs.html , the project code I developed used useFormState (e.g. in app\ui\invoices\create-form.tsx), But now in Sept. 2024, the next.js tutorial page has code with useActionState in place of useFormState!

Explanation of it is found in: React 19 RC, https://react.dev/blog/2024/04/25/react-19 dated April 25, 2024 :
"React.useActionState was previously called ReactDOM.useFormState in the Canary releases, but we’ve renamed it and deprecated useFormState."
RC in 'React 19 RC', seems to stand for Release Candidate. So it does not seem to be an official release yet; react documentation reference for useActionState, https://react.dev/reference/react/useActionState , (seen on 21 Sep. 2024) mentions React version as react@18.3.1. The page states, "The useActionState Hook is currently only available in React’s Canary and experimental channels."

[React 18 vs React 19 (RC): Key Differences and Migration Tips with Examples, https://dev.to/manojspace/react-18-vs-react-19-key-differences-and-migration-tips-18op .]

=======================

Error Handling in Zod, https://zod.dev/ERROR_HANDLING is a detailed page. Relevant content to understand next.js tutorial code plus some related stuff are given below:

ZodError is the validation error(s) object:
class ZodError extends Error {
  issues: ZodIssue[];
}
----

ZodIssue is a discriminated union and not a class.
Discriminated unions, https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions gives a nice example of it. The key code is as follows:
interface Circle {
  kind: "circle";
  radius: number;
}
 
interface Square {
  kind: "square";
  sideLength: number;
}
 
type Shape = Circle | Square;
...

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
                        
    case "square":
      return shape.sideLength ** 2;
             
  }
}
----
TypeScript is able to view above code as correct as it knows that radius is used only when shape.kind is circle and sideLength is used only when shape.kind is square.

"When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can narrow out the members of the union."

Back to ZodIssue ...
ZodIssue has 3 common properties:
code - enum describing the error issue for the schema field
path - (string | number)[] - e.g. ['addresses', 0, 'line1'] - The array identifies the schema field having this issue.
message - string - error message, e.g. Invalid type. Expected string, received number.

If the schema has nested fields then path, I think, will need to have enough array elements to identify the field having the issue.

A demonstrative example, https://zod.dev/ERROR_HANDLING?id=a-demonstrative-example is a good example of a quite simple but nested object schema being 'parsed' with incorrect ('improperly formatted') data, and so throwing an error of type z.ZodError with a list of issues.

Error handling for forms, https://zod.dev/ERROR_HANDLING?id=error-handling-for-forms first has an object schema with one level of nesting, which is conveniently handled in JSX code by using ZodError.format().

Flattening errors, https://zod.dev/ERROR_HANDLING?id=flattening-errors gives an example of an object schema which is only one level deep (no nesting) for which errors can be conveniently handled in JSX code by using ZodError.flatten(). The next.js tutorial uses ZodError.flatten() for its create invoice form. Related code:
In app\lib\actions.ts:
export async function createInvoice(prevState: State, formData: FormData) {
  const validatedFields = CreateInvoice.safeParse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
  // If form validation fails, return errors early. Otherwise, continue.
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: 'Missing Fields. Failed to Create Invoice.',
    };
  }

...[Code that inserts row in database snipped]
  } catch (error) {
    return {
      message: 'Database Error: Failed to Create Invoice.',
    };
  }
----

In app\ui\invoices\create-form.tsx, the form level code is:
export default function Form({ customers }: { customers: CustomerField[] }) {
  const initialState = { message: null, errors: {} };
  const [state, dispatch] = useFormState(createInvoice, initialState);
  return (
    <form action={dispatch}>
...
----

In above code, for createInvoice, VSCode Intellisense provides:
(alias) function createInvoice(prevState: State, formData: FormData): Promise<{
    errors: {
        customerId?: string[] | undefined;
        amount?: string[] | undefined;
        status?: string[] | undefined;
    };
    message: string;
} | {
    message: string;
    errors?: undefined;
}>
import createInvoice
----

To show individual field level error in the form, the code for customer is:
          <div id="customer-error" aria-live="polite" aria-atomic="true">
            {state.errors?.customerId &&
              state.errors.customerId.map((error: string) => (
                <p className="mt-2 text-sm text-red-500" key={error}>
                  {error}
                </p>
              ))}
          </div>
----
To show form level error message, the code is (note that createInvoice provides the form level error message for individual field error(s) case, and also for case of no individual field error but a database error on attempting to create the invoice in the database:
        <div id="error-message" aria-live="polite" aria-atomic="true">
          {state.message ? (
            <p className="mt-2 text-sm text-red-500">{state.message}</p>
          ) : null}
        </div>
----
============
In my tutorial project, I have not made changes to update invoice code to incorporate zod error messages like create invoice code. These changes seem to be quite straight forward stuff very similar to create invoice code. Perhaps that's why I skipped it. In this round, I don't want to get into updating the project code. I am simply studying the existing code to understand it better.

I have finished with chapters 12, 13 and 14. Next chapter is 15: Adding Authentication.
======================

Comments