HaLamUs / nextjs-13

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

nextjs-13

First

This project is my REACTION to the video ⬇️

IMAGE ALT TEXT

Ref

https://nextjs.org/blog/next-13

App directory

app/page.tsx

function Home() {
  return <div> I am the homepage </div>
}
  • Delete index.tsx to able to use new style home page

This will auto create a file called layout.tsx this file is a handy way to share different UI component in sort of hierachical fashion way.

The pros: preverse the state and prevent unnecessarily re-rendering

app/layout.tsx

🧠 The layout.tsx is the way you define the layout for parent and childrent pages.

You can always overide this.

export default function RootLayout({children})
return (
  <html>
    <head></head>
    <body> 
      <Header />
      {children}
     </body>
  </html>
)

app/Header.tsx

function Header() {
  return (
    <header> 
      <Link href="/">This is a Header </Link>
    </header>
  )
}

Create another page

app/todos/page.tsx

function Todos() {
  return (
    <div>
      <TodosList />
    </div>
  )
}

Access: localhost:3000/todos

🟡 All components are inside the app is server component

app/todos/TodosList.tsx

const fetchTodos = async () => {
  const res = await fetch(`lol.me`)
  const todos: Todo[] = await res.json();
  console.log(`Todos`, todos) // 🔴 This one ONLY in terminal coz this component belongs to server side 
  return todos;
}

function TodosList() {
  const todos = await fetchTodos()

  return (
    {
      todos.map((todo)=>(
        <p key={todo.id}>
          <Link href={`/todos/${todo.id}`}> Todo: {todo.id} </Link>
        </p>
      ))
    }
  )
}

This is the typescript way ⏬

typing.d.ts

export type Todo = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

Dynamic routes

To prevent 404 for item details page

app/todos/[todoId]/page.tsx

type PageProps = {
  params: {
    todoId: string;
  }
}

const fetchTodo = async (todoId: string) => {
  // const res = await fetch(`lol.me/${todoId}`))
  // update fetch api using cache 
  // const res = await fetch(`lol.me/${todoId}`), { cache: 'force-cache' })
  // no-cache: meaing sever side rendering 
  // force-cache: static generation 

  const res = await fetch(`lol.me/${todoId}`), { cache: { revalidate: 60 } })
  const todo: Todo = await res.json()
  return todo;
}

async function TodoPage({ params: { todoId }}: PageProps) {
  const todo = await fetchTodo(todoId)

  if (!todo.id) {
    return notFound()
  }

  return (
    <div>
      <p> {todo.id}: {todo.title} </p>
    </div>
  )
}

Terms

Server side rendering

Meaning async loading, you are waiting while geting data from other service then render it

Static rendering

Meaning you are already had a static website

Render in advange

We will pre-initialize the page at build time. When you pack the website to deploy we will pre-render first (10) item details by using func generateStaticParams

Remember this function only run at build time

export async function generateStaticParams() {
  const res = await fetch(`lol.me`)
  const todos: Todo[] = await res.json();
  /*
    We need return data in this format 
    [{todoId: '1'}, {todoId: `2`}, ...]
  */
 const trimmedTodos = todo.splice(0, 10);
 return trimmedTodos.map((todo) => ({
  todoId: todo.id.toString()
 }))
  
}

Ok, if we access page out of 10

  • First, it will server load
  • Second, it cache the page for next time

dynamicParams

Ref: https://nextjs.org/docs/app/api-reference/file-conventions/ route-segment-config

export const dynamicParams = true // true | false,
  • true (default): Dynamic segments not included in generateStaticParams are generated on demand.
  • false: Dynamic segments not included in generateStaticParams will return a 404.

app/todos/[todoId]/not-found.tsx

function NotFound() {
  return <div> Whoops! </div>
}

app/todos/layout.tsx

export default function RootLayout() {
  return (
    <main>
      <TodoList />
      /* 🔴 the children is the rest of the page */
      <div> {children} </div>
    </main>
  )
}

Replace app/todos/page.tsx

function Todos() {
  return (
    <div>
      <h1> This is where the item detail will be render </h1>
    </div>
  )
}

🔴 With this layout.tsx we will see a list 1... n to-do list on the left and this text This is where the item detail will be render from page.tsx from the right

WHEN tap any item on the LEFT the text This is where the item detail will be render will be replaced by the to-do-detail

we always keep the todolist only render the item details next to the list, meaning the item list will not re-render. Layout will devide by we-want-section, boosting performance

app/search/page.tsx

function Search() {
  return <div> Search </div>
}

Access: localhost:3000/search

app/search/layout.tsx

export default function RootLayout (
  return (
    <main>
      <h1> Search </h1>
      <div className="flex-1 pl-5">
        <Search />
        // the rest of the page is children
        <div> {children} </div>
      </div>
    </main>
  )
)

app/search/Search.tsx

function Search() {
  return (
    <h1> Search component </h1>
  )
}

app/search/page.tsx

function Search() {
  return <h1> Search </h1>
}

Access: localhost:3000/search

app/search/layout.tsx

export default function RootLayout {
  return (
    <main>
      <div>
        <h1> Search </h1>
      </div>
      <div>
      </div>
        <Search />
        <div> {children} </div>
    </main>
  )
}

With this layout text Search in page.tsx will be replace when hit search btn. Then access localhost:3000/search/halamhandsome

app/search/Search.tsx

Apply client component

`use client`

function Search() {
  const [search, setSearch] = useState("")
  const router = useRouter()

  const handleSearch = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setSearch("")
    router.push(`/search/${search}`)
  };
  return (
    <form onSubmi={handleSearch}>
      <input type="text" value={search} placeholder="Enter the Search term" onChane={(e) => setSearch(e.target.value)} />
      <button type="submit" className="btn"> Search </button>
    </form>
  )


}

app/[searchTerm]/page.tsx

type PapeProps = {
  params: {
    searchTerm: string;
  }
}

type SearchResult = {
  organic_results: [
    {
      position: number;
      title: string;
      link: string;
      thumbnail: string;
    }
  ]
}

const search = async (searchTerm: string) => {
  const res = await fetch(
    `https://me.lam/search?q=${searchTerm}`
  )
  throw new Error('WHOOPS something broke')
  const data: SearchResult = await res.json()
  return data;
}

async function SearchResults( { params: { searchTerm } }: PageProps ) {
  const searchResults = await search(searchTerm);
  return (
      <div>
        <p> You searched for: {searchTerm}</p>
        <ol>
        {
          searchResults.organic_results.map((result)=>(
            <li key={result.position}>
              <p> {result.title} </p>
            </li>
          ))
        }
        </ol>
      </div> 
    )
}

app/search/[searchTerm]/loading.tsx

function Loading() {
  return <div> Loading ... </div>
}

app/search/[searchTerm]/error.tsx

'use client';

export default function Error() {
  return (
    <div>
      <p> Something went wrong! </p>
      <button onClick={() => reset()}> Reset error boundary </button>
    </div>
  )
}

app/search/head.tsx

function Head() {
  return (
    <title>
      The search page
    </title>
  )
}

Rout groups

You want seperate the user and admin without adding the localhost/admin/list

https://nextjs.org/docs/app/building-your-application/routing/route-groups

Example: /(user)/search /(user)/todo /(admin)/develop

Fallback

function Home() {
  return (
    <div>
      <Suspense fallback=(<p> Loading ... </p>)>
      <TodoList />
      </Suspense>
    </div>
  )
}

Page and layout

https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts

When to use Server and Client components?

https://nextjs.org/docs/getting-started/react-essentials#when-to-use-server-and-client-components

Client: Need browser api, statefullness (hook, useState)

Author

This repo was developed by @lamha. Follow or connect with me on my LinkedIn.

License

About