생활코딩

Coding Everybody

생성 기능 구현 - Client component

토픽 생활코딩 > WEB > WEB2 - JavaScript > React > Next.js 13

생성 기능을 구현하기 위해서는 onSubmit과 같은 사용자와 상호작용하는 코드가 필요합니다. 이런 코드는 서버 쪽에서 실행할 수 없기 때문에 클라이언트 쪽으로 전송되어서 실행되야 합니다. 여기서는 클라이언트 컴포넌트를 만드는 방법을 살펴봅니다. 

소스코드

https://github.com/egoing/nextapp/commit/0a316f1e31a52729607bc190d5c5e65483a71e20

절차

1. app/create/layout.js 삭제

이 파일은 중첩된 layout을 보여주기 위해서 만든 임시 파일이기 때문에 삭제 합니다. 

2. app/create/page.js 수정

'use client'
 
import { useRouter } from "next/navigation";
 
export default function Create(){
  const router = useRouter();
  return <form onSubmit={async evt=>{
    evt.preventDefault();
    const title = evt.target.title.value;
    const body = evt.target.body.value;
    const resp = await fetch('http://localhost:9999/topics/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({title, body})
    });
    const topic = await resp.json();
    console.log("file: page.js:19 ~ Create ~ topic:", topic)
    router.push(`/read/${topic.id}`);
    router.refresh();
  }}>
    <h2>Create</h2>
    <p><input type="text" name="title" placeholder="title" /></p>
    <p><textarea name="body" placeholder="body"></textarea></p>
    <p><input type="submit" value="create" /></p>
  </form>
}

3.  server component -> client component

'use client'

위의 코드를 사용하면 client component로 전환됩니다. 클라이언트 컴포넌트가 되면 useEffect, useState, onSubmit과 같은 코드를 사용할 수 있게 됩니다. 

4. 라우터

const router = useRouter();

useRouter를 사용하면 라우터 객체를 생성할 수 있습니다. useRouter는 client component에서만 사용 가능합니다. 

5. 라우터 사용

router.push(`/read/${topic.id}`);
router.refresh();

router.push를 사용하면 페이지 리로드 없이 사용자의 화면을 해당 페이지로 이동합니다. router.refresh를 사용하면 서버 컴포넌트를 서버 쪽에서 다시 랜더링해서 새로 고침할 수 있습니다. 여기서는 app/layout.js을 새로고침하기 위해서 사용된 코드입니다.

6. cache 업데이트

router.refresh를 했음에도 글 목록이 갱신되지 않을 것입니다. 그 이유는 서버쪽에서 fetch를 사용하면 응답 결과를 저장하기 때문입니다. 개발 서버를 다시 시작하고, 페이지를 리로드한 후에 터미널을 봅시다. 

루트 페이지(/)로 접속했을 때 app/layout.js에서 fetch가 동작해서 http://localhost:3000/api/topics/로 접속이 발생했다는 뜻입니다. cache : MISS는 캐쉬가 없기 때문에 서버에 실제로 접속해서 데이터를 가져왔다는 뜻입니다. 

 다시 접속하면 아래와 같이 됩니다. 

cache : HIT 입니다. 캐쉬를 사용했다는 뜻입니다. 

캐쉬를 삭제한 후에 router.refresh를 하면 됩니다만, 그건 수업의 범위를 벗어나기 때문에 fetch를 하는 단계에서 캐쉬를 사용하지 않는 방법을 보여드리겠습니다. 자세한 내용은 revalidate를 확인해주세요.

7. app/layout.js 편집

import Link from 'next/link'
import './globals.css'
export const metadata = {
  title: 'WEB tutorial',
  description: 'Generated by egoing',
}
export default async function RootLayout({ children }) {
  const resp = await fetch('http://localhost:9999/topics/', {cache:'no-cache'}) 
  const topics = await resp.json();
  console.log('page/layout.js/topics', topics)
  return (
    <html>
      <body>
        <h1><Link href="/">WEB</Link></h1>
        <ol>
          {topics.map(topic=>{
            return <li key={topic.id}><Link href={`/read/${topic.id}`}>{topic.title}</Link></li>
          })}
        </ol>
        {children}
        <ul>
          <li><Link href="/create">create</Link></li>
          <li><Link href="/update/id">update</Link></li>
          <li><button>delete</button></li>
        </ul>
      </body>
    </html>
  )
}

위의 코드는 아래 부분이 변경되었습니다. 

const resp = await fetch('http://localhost:9999/topics/', {cache:'no-cache'}) 

{cache:'no-cache'}를 추가하면 캐쉬를 사용하지 않게 됩니다. 

이제 layout.js의 fetch는 랜더링 될 때마다 캐쉬를 사용하지 않고 신선한 데이터를 가져오게 되었습니다. 

 

댓글

댓글 본문
  1. 호마
    read의 page.js의 Read()에
    const topic = resp.json()...을 다음과 같이 비동기로 하지 않으면 부정확하게 표현됩니다
    const topic = await resp.json()...
  2. Sansol Park
    제공해주신 에러 메시지를 보니, 'fetch' 함수가 URL을 제대로 파싱하지 못했다는 내용이 보입니다. 이 문제는 대게 fetch 함수를 호출할 때 제공된 URL이 잘못되었을 때 발생합니다.

    보통 다음과 같은 상황에서 이런 문제가 발생할 수 있습니다:

    1. URL이 올바르게 작성되지 않았을 때: URL이 올바른 형식을 따르지 않거나, 필요한 프로토콜(http, https 등)이 누락되었을 경우 발생할 수 있습니다.

    2. 상대 경로가 잘못 사용되었을 때: fetch 함수는 기본적으로 절대 URL을 요구합니다. 상대 경로를 사용할 경우, 현재 위치에 따라 예상치 못한 결과를 가져올 수 있습니다.

    3. 환경 변수 등을 통해 URL이 주입되는 경우, 해당 변수가 올바르게 설정되지 않았을 때: 이 경우, 환경 설정을 확인하고 필요한 경우 수정이 필요합니다.

    해결 방법은 다음과 같습니다:

    1. fetch 함수를 호출하는 코드를 확인하세요. URL이 올바른지, 필요한 프로토콜이 포함되어 있는지 확인해 보세요.

    2. URL이 변수로 주입되는 경우, 해당 변수의 값을 확인하세요. 환경 변수 등을 통해 URL이 주입되는 경우, 해당 변수가 올바르게 설정되었는지 확인해 보세요.

    3. 만약 상대 경로를 사용하고 있다면 절대 경로로 변경해 보세요.
    에러 메시지가 '://......ics'로 끝나는 것으로 보아, URL이 올바르게 설정되지 않았을 가능성이 높아 보입니다. URL을 다시 확인해 보시는 것이 좋겠습니다.

    GPT-4를 활용한 답변입니다.
    대화보기
    • Sansol Park
      유튜브 생활코딩 next.js 강의에서 설명하시는 것처럼, 일반적으로 router.refresh()와 router.push()의 실행 순서는 서로 영향을 미치지 않아야 합니다. 하지만, 실제로는 어떤 경우에는 router.push()를 먼저 실행해야만 제대로 동작하는 경우가 있을 수 있습니다.

      이는 Next.js의 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR) 사이의 동기화 문제 때문일 수 있습니다. 페이지를 이동하는 router.push()가 먼저 실행되면, 해당 페이지는 클라이언트 사이드에서 새로 렌더링되며, 이후 router.refresh()가 실행되어 서버 사이드에서 페이지를 다시 렌더링합니다.

      이 과정에서 SSR과 CSR 사이의 동기화 문제가 발생할 수 있어, router.push()를 먼저 실행하지 않으면 페이지 이동 후 새로고침이 제대로 반영되지 않을 수 있습니다.

      이 문제를 해결하기 위해 router.push()를 먼저 실행하거나, 또 다른 방법으로는 데이터를 가져오는 API 호출에 revalidate 옵션을 추가하여, 데이터가 변경될 때마다 서버 사이드에서 자동으로 페이지를 새로 렌더링하도록 설정하는 방법도 있습니다.

      다만, 이는 제 개인적인 해석이며, 정확한 원인을 파악하기 위해서는 Next.js의 내부 동작 방식을 좀 더 깊이 이해하는 것이 필요합니다. 각 프로젝트의 환경과 설정도 이러한 동작에 영향을 줄 수 있으므로, 개별적인 상황에 따라 적절한 해결 방법을 찾는 것이 중요합니다.

      GPT-4를 활용한 답변입니다.
      대화보기
      • shinebyul
        router.push(`/read/${lastid}`);
        router.refresh();

        이론적으로 생각해봤을 때 이 두개의 코드는 서로 영향을 미치지 않을 것으로 생각이되고,

        실제로도 opentutorials에서는 push로 페이지 전환이 먼저 되고 refresh로 새로고침을 하고,
        유튜브 생활코딩 next.js강의에서는 refresh로 새로고침이 먼저 되고 push로 페이지 전환이 되는데,
        두 방법 다 작동이 잘 되는 것을 볼 수 있었습니다.

        하지만 유튜브에 있는 코드 그대로 작성했을 때 저는 refresh를 먼저 하고 push를 하면 글목록에 바로 적용이 안되고, push를 먼저 하고 refresh를 해야만 글목록에 적용이 됩니다.
        왜그런건지 알 수 있을까요?!
      • 카르민코프1
        여기까지 하다가 캐시만들 때(13강) 뭔가 문제가 생겼는지 갑자기 사이트에 페이지가 안 뜨네요
        Unhandled Runtime Error
        TypeError: Failed to execute 'fetch' on 'Window': Failed to parse URL from ://......ics

        어떻게 해결해야 할까요?