생활코딩! React 리액트 프로그래밍

egoing - React - update

lshjju 2025. 6. 24. 23:51

글 수정 기능 만들기



상세페이지에서만 업데이트 링크가 노출되도록 합니다.


export default function App() {
  const [mode, setMode] = useState("WELCOME");
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    { id: 1, title: "html", body: "html is ..." },
    { id: 2, title: "css", body: "css is ..." },
    { id: 3, title: "javascript", body: "javascript is ..." },
  ]);
  let content = null;
  let contextControl = null;
  if (mode === "WELCOME") {
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>;
    contextControl = (
      <li>
        <a href={"/update/" + id}>Update</a>
      </li>
    );
  } else if (mode === "CREATE") {
    content = (
      <Create
        onCreate={(_title, _body) => {
          const newTopic = { id: nextId, title: _title, body: _body };
          const newTopics = [...topics];
          newTopics.push(newTopic);
          setTopics(newTopics);
          setMode("READ");
          setId(nextId);
          setNextId(nextId + 1);
        }}
      ></Create>
    );
  }
  return (
    <div className="App">
      <Header
        title="WEB"
        onChangeMode={() => {
          setMode("WELCOME");
        }}
      ></Header>
      <Nav
        topics={topics}
        onChangeMode={(_id) => {
          setMode("READ");
          setId(_id);
        }}
      ></Nav>
      {content}
      <a
        href="/create"
        onClick={(event) => {
          event.preventDefault();
          setMode("CREATE");
        }}
      >
        Create
      </a>
      {contextControl}
    </div>
  );
}

 

Completion


<li><a href="/update">Update</a></li>

 

펑션앱 리턴 맨 아래 에이태그 아래

 

업데이트 유아이버튼 추가합니다.


let contextControl = null;

 

펑션앱 모드웰컴 바로 위

 

업데이트 유아이버튼을 담을 변수를 초기화 합니다.

  // 현재 'mode' 값에 따라 표시될 추가적인 제어 버튼(예: Update)을 저장할 변수입니다.
  // 초기값은 null이므로, 기본적으로는 아무것도 표시되지 않습니다.
  let contextControl = null;

contextControl = <li><a href={'/update/'+id}>Update</a></li>

 

펑션앱 모드리드 콘텐트변수 바로 아래

 

리드모드에서 업데이트 유아이가 출력되도록 합니다.

리드모드 에 자리 잡습니다.

유알엘에 아이디밸류도 추가합니다.

    // 'READ' 모드일 때만, 현재 id에 해당하는 내용을 수정할 수 있는 "Update" 링크를 `contextControl`에 할당합니다.
    contextControl = <li><a href={'/update/'+id}>Update</a></li>

{contextControl}

 

펑션앱 리턴 맨 아래 업데이트유아이

 

원래 있던 업데이트링크가 변수로 세팅되었습니다.

그러므로 업데이트링크변수를 원래 있던 태그자리에 대체합니다.

      {/* 'contextControl' 변수에 저장된 내용(예: 'Update' 링크)을 렌더링합니다. */}
      {/* 'READ' 모드일 때만 'Update' 링크가 표시됩니다. 그 외 모드에서는 null이므로 아무것도 렌더링되지 않습니다. */}
      {contextControl}

 

test

 

상세페이지에서만 업데이트 유아이가 노출되는지 체크합니다.

 

 



펑션앱에 모드 업데이트 추가 합니다.


export default function App() {
  const [mode, setMode] = useState("WELCOME");
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    { id: 1, title: "html", body: "html is ..." },
    { id: 2, title: "css", body: "css is ..." },
    { id: 3, title: "javascript", body: "javascript is ..." },
  ]);
  let content = null;
  let contextControl = null;
  if (mode === "WELCOME") {
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>;
    contextControl = (
      <li>
        <a
          href={"/update/" + id}
          onClick={(event) => {
            event.preventDefault();
            setMode("UPDATE");
          }}
        >
          Update
        </a>
      </li>
    );
  } else if (mode === "CREATE") {
    content = (
      <Create
        onCreate={(_title, _body) => {
          const newTopic = { id: nextId, title: _title, body: _body };
          const newTopics = [...topics];
          newTopics.push(newTopic);
          setTopics(newTopics);
          setMode("READ");
          setId(nextId);
          setNextId(nextId + 1);
        }}
      ></Create>
    );
  } else if (mode === "UPDATE") {
    content = <Update></Update>;
  }
  return (
    <div className="App">
      <Header
        title="WEB"
        onChangeMode={() => {
          setMode("WELCOME");
        }}
      ></Header>
      <Nav
        topics={topics}
        onChangeMode={(_id) => {
          setMode("READ");
          setId(_id);
        }}
      ></Nav>
      {content}
      <a
        href="/create"
        onClick={(event) => {
          event.preventDefault();
          setMode("CREATE");
        }}
      >
        Create
      </a>
      {contextControl}
    </div>
  );
}

 

Completion


    contextControl = <li><a href={'/update/'+id} onClick={event=>{
      event.preventDefault();
      setMode('UPDATE');
    }}>Update</a></li>

 

펑션앱 모드리드 콘텍스트콘트롤 변수

 

업데이트 탭하면 업데이트 모드로 변경해야 합니다.

변경 기능 추가 합니다.

// 'READ' 모드에서는 'Update' 링크를 `contextControl`에 추가합니다.
    contextControl = <li><a href={'/update/'+id} onClick={event=>{
      event.preventDefault(); // 기본 이벤트를 방지합니다.
      setMode('UPDATE'); // 모드를 'UPDATE'로 변경하여 수정 화면을 표시하도록 요청합니다.
    }}>Update</a></li>

else if(mode === 'UPDATE') {
    content = <Update></Update>
  }

 

펑션앱 모드크리에이트 바로 아래

 

모드가 업데이트일 경우 업데이트를 출력하게 합니다.

엘스이프를 크리에잇 아래에 추가 합니다.



펑션업데이트 추가하고 펑션앱에서 프롭스 받습니다.


function Update(props) {
  return (
    <article>
      <h2>Update</h2>
      <form onSubmit={event=>{
        event.preventDefault();
        const title = event.target.title.value;
        const body = event.target.body.value;
        props.onUpdate(title, body);
      }}>
        <p><input type="text" name="title" placeholder="title" /></p>
        <p><textarea name="body" placeholder="body"></textarea></p>
        <p><input type="submit" value="Update"></input></p>
      </form>
    </article>
  )
}

 

Completion


function Update(props)

<h2>Update</h2>

props.onUpdate(title, body);

<p><input type="submit" value="Update"></input></p>

 

펑션앱 바로 위

 

펑션 업데이트를 펑션앱 바로 위에 추가 합니다.

크리에잇 펑션을 카피해서 사용하면 됩니다.

일단 크리에잇 펑션을 카피 합니다.

펑션앱 바로 위에 페이스트 합니다.

일부 코드를 업데이트에 맞게 수정 합니다.

수정해야 할 코드는 위와 같습니다.

// 기존 내용을 수정하는 폼을 제공하는 `Update` 컴포넌트입니다.
// `props`로 `onUpdate` 함수를 전달받아 폼 제출 시 호출합니다.
function Update(props) {
  return (
    <article>
      <h2>Update</h2> {/* '수정'이라는 제목을 표시합니다. */}
      {/* 폼 제출 시 `onSubmit` 이벤트 핸들러가 호출됩니다. */}
      <form onSubmit={event=>{
        event.preventDefault(); // 기본 폼 제출 동작(페이지 리로드)을 방지합니다.
        // 입력 필드(`title`, `body`)에서 값을 가져옵니다.
        const title = event.target.title.value;
        const body = event.target.body.value;
        // `onUpdate` prop으로 전달받은 함수를 호출하여 수정된 제목과 본문을 전달합니다.
        props.onUpdate(title, body);
      }}>
        {/* 제목 입력 필드입니다. */}
        <p><input type="text" name="title" placeholder="title" /></p>
        {/* 본문 입력 필드입니다. */}
        <p><textarea name="body" placeholder="body"></textarea></p>
        {/* 제출 버튼입니다. */}
        <p><input type="submit" value="Update"></input></p>
      </form>
    </article>
  )
}

  if (mode === "WELCOME") {
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>;
    contextControl = (
      <li>
        <a
          href={"/update/" + id}
          onClick={(event) => {
            event.preventDefault();
            setMode("UPDATE");
          }}
        >
          Update
        </a>
      </li>
    );
  } else if (mode === "CREATE") {
    content = (
      <Create
        onCreate={(_title, _body) => {
          const newTopic = { id: nextId, title: _title, body: _body };
          const newTopics = [...topics];
          newTopics.push(newTopic);
          setTopics(newTopics);
          setMode("READ");
          setId(nextId);
          setNextId(nextId + 1);
        }}
      ></Create>
    );
  } else if (mode === "UPDATE") {
    content = <Update onUpdate={(title, body) => {}}></Update>;
  }

 

Completion


    content = <Update onUpdate={(title, body)=>{
      
    }}></Update>

 

펑션앱 모드업데이트 콘텐트변수

 

펑션업데이트에 프롭스를 전달해야 합니다.

콘텐트 변수를 업데이트 합니다.

// 'UPDATE' 모드일 경우 글 수정 폼을 제공하는 `Update` 컴포넌트를 렌더링합니다.
    content = <Update onUpdate={(title, body)=>{ // `Update` 컴포넌트에서 글 수정이 완료되면 호출됩니다.
      // 이 부분에 `topics` 배열 내에서 해당 `id`를 가진 토픽의 `title`과 `body`를 업데이트하는 로직이 필요합니다.
      // 예를 들어, `setTopics`를 사용하여 `topics` 배열을 갱신하고, 이후 `setMode('READ')`를 호출하여 읽기 모드로 전환할 수 있습니다.
    }}></Update>

test

 

상세페이지에서 업데이트링크 탭하면 유아이기 잘 출력되는지 체크 합니다.


 


펑션업데이트폼에 보낼 데이터를 준비 합니다.


else if (mode === "UPDATE") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      console.log(topics[i].id, id);
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = (
      <Update title={title} body={body} onUpdate={(title, body) => {}}></Update>
    );
  }

 

Completion


else if(mode === 'UPDATE') {
    let title, body = null;
    for(let i=0; i<topics.length; i++) {
      console.log(topics[i].id, id);
      if(topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Update onUpdate={(title, body)=>{

    }}></Update>
  }

 

펑션앱 모드업데이트

 

업데이트 할 데이터를 가져와야 합니다.

이전에 리드 모드에서 데이터를 가져온 적이 있습니다.

그렇다면 리드 모드에서 그 코드를 훔쳐오면 되겠습니다.

펑션앱 모드리드 내부 코드를 카피 합니다.

펑션앱 모드업데이트 내부에 페이스트 합니다.

else if(mode === 'UPDATE') {
    // 'UPDATE' 모드일 경우 글 수정 폼을 제공하는 `Update` 컴포넌트를 렌더링합니다.
    let title, body = null;
    // 현재 선택된 ID(`id`)에 해당하는 토픽을 `topics` 배열에서 찾아옵니다.
    for(let i=0; i<topics.length; i++) {
      console.log(topics[i].id, id); // 디버깅을 위해 현재 토픽 ID와 선택된 ID를 콘솔에 출력합니다.
      if(topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Update onUpdate={(title, body)=>{
      
    }}></Update>
  }

    content = <Update title={title} body={body} onUpdate={(title, body)=>{

    }}></Update>

 

펑션앱 모드업데이트 콘텐트변수

 

위에서 추출한 데이터를 펑션업데이트로 전송해야 합니다.

펑션업데이트에 보낼 데이터를 탑승 시킵니다.

    // 찾아온 `title`과 `body`를 `Update` 컴포넌트에 props로 전달하여 입력 필드의 초기 값으로 설정합니다.
    content = <Update title={title} body={body} onUpdate={(title, body)=>{
      // 이 `onUpdate` 함수는 `Update` 컴포넌트에서 제출 버튼 클릭 시 호출됩니다.
      // 여기에 `topics` 배열 내에서 해당 `id`를 가진 토픽의 `title`과 `body`를
      // 전달받은 값으로 업데이트하는 로직이 필요합니다.
      // 예를 들어, `setTopics`를 사용하여 `topics` 배열을 갱신하고,
      // 이후 `setMode('READ')`를 호출하여 읽기 모드로 전환할 수 있습니다.
    }}></Update>


업데이트폼에 기존데이터바인딩 합니다.


function Update(props) {
  return (
    <article>
      <h2>Update</h2>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const title = event.target.title.value;
          const body = event.target.body.value;
          props.onUpdate(title, body);
        }}
      >
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={props.title}
          />
        </p>
        <p>
          <textarea
            name="body"
            placeholder="body"
            value={props.body}
          ></textarea>
        </p>
        <p>
          <input type="submit" value="Update"></input>
        </p>
      </form>
    </article>
  );
}

 

Completion


        <p><input type="text" name="title" placeholder="title" value={props.title} /></p>
        <p><textarea name="body" placeholder="body" value={props.body} ></textarea></p>

 

펑션업데이트 내부

 

폼태그에 프롭스로 받은 수정할 기존 데이터를 바인딩 합니다.

        {/* 제목 입력 필드입니다. `props.title`을 기본 값으로 가집니다. */}
        <p><input type="text" name="title" placeholder="title" value={props.title} /></p>
        {/* 본문 입력 필드입니다. `props.body`를 기본 값으로 가집니다. */}
        <p><textarea name="body" placeholder="body" value={props.body} ></textarea></p>

test

네브탭하고 업데이트폼에 기존데이터 잘 불러오는지 체크 합니다.

 

 



펑션업데이트에 데이터갱신 이벤트 추가 합니다.


function Update(props) {
  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);
  return (
    <article>
      <h2>Update</h2>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const title = event.target.title.value;
          const body = event.target.body.value;
          props.onUpdate(title, body);
        }}
      >
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={title}
            onChange={(event) => {
              setTitle(event.target.value);
            }}
          />
        </p>
        <p>
          <textarea
            name="body"
            placeholder="body"
            value={body}
            onChange={(event) => {
              setBody(event.target.value);
            }}
          ></textarea>
        </p>
        <p>
          <input type="submit" value="Update"></input>
        </p>
      </form>
    </article>
  );
}

 

Completion


  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);

 

펑션 업데이트 리턴 위

 

업데이트를 하려면 기존의 데이터 상태를 변경해야 합니다.

그렇다면 당연히 데이터 상태를 변경할 수 있도록 제목/내용을 상태변경 데이터인 스테이트로 초기화 합니다.

  // `useState` 훅을 사용하여 입력 필드의 현재 값을 관리합니다.
  // `props`로 받은 초기 값을 상태 변수에 할당합니다.
  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);

        <p><input type="text" name="title" placeholder="title" value={title} onChange={event=>{
          setTitle(event.target.value);
        }}/></p>
        <p><textarea name="body" placeholder="body" value={body} onChange={event=>{
          setBody(event.target.value);
        }}></textarea></p>

 

펑션업데이트 리턴 인풋/텍스트에어리어태그

 

뷰 데이터도 스테이트로 변경합니다.

데이터를 수정할 때마다 스테이트가 변경되는 기능이 필요합니다.

온체인지 데이터를 프롭스 데이터가 아닌 스테이트 데이터로 변경 합니다.

리액트 온체인지는 데이터를 수정할 때마다 데이터를 갱신합니다.

{/* 제목 입력 필드입니다. `value`는 상태 변수 `title`과 연결되어 있으며, */}
        {/* `onChange` 이벤트 발생 시 `setTitle` 함수를 통해 `title` 상태를 업데이트합니다. */}
        <p><input type="text" name="title" placeholder="title" value={title} onChange={event=>{
          setTitle(event.target.value);
        }}/></p>
        {/* 본문 입력 필드입니다. `value`는 상태 변수 `body`와 연결되어 있으며, */}
        {/* `onChange` 이벤트 발생 시 `setBody` 함수를 통해 `body` 상태를 업데이트합니다. */}
        <p><textarea name="body" placeholder="body" value={body} onChange={event=>{
          setBody(event.target.value);
        }}></textarea></p>


수정 데이터 업데이트 합니다.


else if (mode === "UPDATE") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      console.log(topics[i].id, id);
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = (
      <Update
        title={title}
        body={body}
        onUpdate={(title, body) => {
          console.log(title, body);
          const newTopics = [...topics];
          const updatedTopic = { id: id, title: title, body: body };
          for (let i = 0; i < newTopics.length; i++) {
            if (newTopics[i].id === id) {
              newTopics[i] = updatedTopic;
              break;
            }
          }
          setTopics(newTopics);
          setMode("READ");
        }}
      ></Update>
    );
  }

 

Completion


    content = (
      <Update
        title={title}
        body={body}
        onUpdate={(title, body) => {
          console.log(title, body);
          const newTopics = [...topics];
          const updatedTopic = { id: id, title: title, body: body };
          for (let i = 0; i < newTopics.length; i++) {
            if (newTopics[i].id === id) {
              newTopics[i] = updatedTopic;
              break;
            }
          }
          setTopics(newTopics);
          setMode("READ");
        }}
      ></Update>
    );

 

펑션앱 모드업데이트 콘텐트 변수

 

콘솔로 데이터 바인딩이 잘 되었는지 체크 합니다.

원본 데이터를 복제해 둡니다.

유저가 수정한 데이터를 변수에 잘 담습니다.

 

현재 내용의 아이디가 무엇인지는 이미 알고 있습니다.

복제 데이터 내용중에 현재 아이디와 일치하는 아이디를 가진 내용을 찾습니다.

찾았다면 그 내용을 복제 데이터 내용에 덮어 씌웁니다.

 

스테이트변수에 복제데이터를 저장합니다.

현재 내용 출력하는 페이지로 이동 합니다.

    // 찾아온 `title`과 `body`를 `Update` 컴포넌트에 props로 전달하여 입력 필드의 초기 값으로 설정하고,
    // `onUpdate` 함수를 전달하여 수정 완료 시 호출될 수 있도록 합니다.
    content = ( <Update title={title} body={body} onUpdate={(title, body)=>{
      console.log(title, body); // 수정된 제목과 본문 내용을 콘솔에 출력합니다.
      const newTopics = [...topics]; // `topics` 배열을 복사하여 새 배열을 생성합니다.
      const updatedTopic = {id:id, title:title, body:body} // 수정된 내용으로 새 토픽 객체를 만듭니다.
      // `newTopics` 배열에서 `id`가 일치하는 토픽을 찾아 수정된 토픽으로 교체합니다.
      for(let i=0; i<newTopics.length; i++) {
        if(newTopics[i].id === id) {
          newTopics[i] = updatedTopic;
          break; // 해당 토픽을 찾았으므로 루프를 종료합니다.
        }
      }
      setTopics(newTopics); // 업데이트된 `newTopics`로 `topics` 상태를 설정합니다.
      setMode('READ'); // 수정이 완료되었으므로 'READ' 모드로 전환합니다.
        }}></Update> );

test

 

 

리스트 아무거나 하나 수정 잘 되는지 체크 합니다.


Completion

import "./styles.css";
import { useState } from "react";

function Header(props) {
  console.log("props", props.title);
  return (
    <header>
      <h1>
        <a
          href="/"
          onClick={(event) => {
            event.preventDefault();
            props.onChangeMode();
          }}
        >
          {props.title}
        </a>
      </h1>
    </header>
  );
}

function Nav(props) {
  const lis = [];
  for (let i = 0; i < props.topics.length; i++) {
    let t = props.topics[i];
    lis.push(
      <li key={t.id}>
        <a
          id={t.id}
          href={"/read/" + t.id}
          onClick={(event) => {
            event.preventDefault();
            props.onChangeMode(Number(event.target.id));
          }}
        >
          {t.title}
        </a>
      </li>
    );
  }
  return (
    <nav>
      <ol>{lis}</ol>
    </nav>
  );
}

function Article(props) {
  return (
    <article>
      <h2>{props.title}</h2>
      {props.body}
    </article>
  );
}

function Create(props) {
  return (
    <article>
      <h2>Create</h2>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const title = event.target.title.value;
          const body = event.target.body.value;
          props.onCreate(title, body);
        }}
      >
        <p>
          <input type="text" name="title" placeholder="title" />
        </p>
        <p>
          <textarea name="body" placeholder="body"></textarea>
        </p>
        <p>
          <input type="submit" value="Create"></input>
        </p>
      </form>
    </article>
  );
}

function Update(props) {
  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);
  return (
    <article>
      <h2>Update</h2>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const title = event.target.title.value;
          const body = event.target.body.value;
          props.onUpdate(title, body);
        }}
      >
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={title}
            onChange={(event) => {
              setTitle(event.target.value);
            }}
          />
        </p>
        <p>
          <textarea
            name="body"
            placeholder="body"
            value={body}
            onChange={(event) => {
              setBody(event.target.value);
            }}
          ></textarea>
        </p>
        <p>
          <input type="submit" value="Update"></input>
        </p>
      </form>
    </article>
  );
}

export default function App() {
  const [mode, setMode] = useState("WELCOME");
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    { id: 1, title: "html", body: "html is ..." },
    { id: 2, title: "css", body: "css is ..." },
    { id: 3, title: "javascript", body: "javascript is ..." },
  ]);
  let content = null;
  let contextControl = null;
  if (mode === "WELCOME") {
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>;
    contextControl = (
      <li>
        <a
          href={"/update/" + id}
          onClick={(event) => {
            event.preventDefault();
            setMode("UPDATE");
          }}
        >
          Update
        </a>
      </li>
    );
  } else if (mode === "CREATE") {
    content = (
      <Create
        onCreate={(_title, _body) => {
          const newTopic = { id: nextId, title: _title, body: _body };
          const newTopics = [...topics];
          newTopics.push(newTopic);
          setTopics(newTopics);
          setMode("READ");
          setId(nextId);
          setNextId(nextId + 1);
        }}
      ></Create>
    );
  } else if (mode === "UPDATE") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      console.log(topics[i].id, id);
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = (
      <Update
        title={title}
        body={body}
        onUpdate={(title, body) => {
          console.log(title, body);
          const newTopics = [...topics];
          const updatedTopic = { id: id, title: title, body: body };
          for (let i = 0; i < newTopics.length; i++) {
            if (newTopics[i].id === id) {
              newTopics[i] = updatedTopic;
              break;
            }
          }
          setTopics(newTopics);
          setMode("READ");
        }}
      ></Update>
    );
  }

  return (
    <div className="App">
      <Header
        title="WEB"
        onChangeMode={() => {
          setMode("WELCOME");
        }}
      ></Header>
      <Nav
        topics={topics}
        onChangeMode={(_id) => {
          setMode("READ");
          setId(_id);
        }}
      ></Nav>
      {content}
      <a
        href="/create"
        onClick={(event) => {
          event.preventDefault();
          setMode("CREATE");
        }}
      >
        Create
      </a>
      {contextControl}
    </div>
  );
}

 


Comment ver

// styles.css 파일을 가져와 애플리케이션의 스타일을 적용합니다.
import "./styles.css";
// React의 useState 훅을 가져와 함수형 컴포넌트에서 상태를 관리할 수 있도록 합니다.
import { useState } from "react";

/**
 * Header 컴포넌트: 애플리케이션의 상단 영역을 담당합니다.
 * @param {object} props - 컴포넌트에 전달되는 속성 객체입니다.
 * @param {string} props.title - 헤더에 표시될 제목입니다.
 * @param {function} props.onChangeMode - 제목 클릭 시 호출될 콜백 함수입니다.
 */
function Header(props) {
  // props.title 값을 콘솔에 출력하여 디버깅 용도로 사용합니다.
  console.log("props", props.title);
  return (
    <header>
      <h1>
        {/* 헤더 제목 링크: 클릭 시 기본 동작을 막고 onChangeMode 함수를 호출하여 페이지 모드를 변경합니다. */}
        <a
          href="/"
          onClick={(event) => {
            event.preventDefault(); // 기본 링크 동작(페이지 새로고침)을 방지합니다.
            props.onChangeMode(); // 부모 컴포넌트로부터 전달받은 모드 변경 함수를 호출합니다.
          }}
        >
          {props.title} {/* props로 전달받은 제목을 표시합니다. */}
        </a>
      </h1>
    </header>
  );
}

/**
 * Nav 컴포넌트: 내비게이션 메뉴를 렌더링합니다.
 * @param {object} props - 컴포넌트에 전달되는 속성 객체입니다.
 * @param {Array<object>} props.topics - 내비게이션 항목으로 사용될 주제(topic) 배열입니다. 각 객체는 id, title, body를 포함합니다.
 * @param {function} props.onChangeMode - 내비게이션 항목 클릭 시 호출될 콜백 함수입니다. 선택된 항목의 id를 인자로 전달합니다.
 */
function Nav(props) {
  const lis = []; // 내비게이션 리스트 아이템(<li>)을 저장할 배열입니다.
  // props로 전달받은 topics 배열을 순회하며 리스트 아이템을 생성합니다.
  for (let i = 0; i < props.topics.length; i++) {
    let t = props.topics[i]; // 현재 topic 객체를 가져옵니다.
    lis.push(
      // 각 topic에 대한 리스트 아이템을 생성합니다.
      <li key={t.id}>
        {/* 내비게이션 링크: 클릭 시 기본 동작을 막고 onChangeMode 함수를 호출합니다. */}
        <a
          id={t.id} // 링크의 id를 topic의 id로 설정하여 클릭 이벤트에서 접근할 수 있도록 합니다.
          href={"/read/" + t.id} // 링크의 URL을 설정합니다.
          onClick={(event) => {
            event.preventDefault(); // 기본 링크 동작(페이지 이동)을 방지합니다.
            // 부모 컴포넌트로부터 전달받은 모드 변경 함수를 호출하며, 클릭된 항목의 id를 숫자로 변환하여 전달합니다.
            props.onChangeMode(Number(event.target.id));
          }}
        >
          {t.title} {/* topic의 제목을 링크 텍스트로 표시합니다. */}
        </a>
      </li>
    );
  }
  return (
    <nav>
      <ol>{lis}</ol> {/* 생성된 리스트 아이템들을 정렬된 리스트(<ol>)로 렌더링합니다. */}
    </nav>
  );
}

/**
 * Article 컴포넌트: 메인 콘텐츠 영역을 담당합니다.
 * @param {object} props - 컴포넌트에 전달되는 속성 객체입니다.
 * @param {string} props.title - 아티클의 제목입니다.
 * @param {string} props.body - 아티클의 본문 내용입니다.
 */
function Article(props) {
  return (
    <article>
      <h2>{props.title}</h2> {/* props로 전달받은 제목을 표시합니다. */}
      {props.body} {/* props로 전달받은 본문 내용을 표시합니다. */}
    </article>
  );
}

/**
 * Create 컴포넌트: 새로운 게시글을 생성하기 위한 폼을 렌더링합니다.
 * @param {object} props - 컴포넌트에 전달되는 속성 객체입니다.
 * @param {function(string, string): void} props.onCreate - 폼 제출 시 호출될 콜백 함수입니다. 제목과 본문 내용을 인자로 전달합니다.
 */
function Create(props) {
  return (
    <article>
      <h2>Create</h2> {/* 'Create' 제목을 표시합니다. */}
      {/* 폼 제출 시 기본 동작을 방지하고, 입력된 제목과 본문으로 onCreate 콜백 함수를 호출합니다. */}
      <form
        onSubmit={(event) => {
          event.preventDefault(); // 폼 제출 시 페이지 새로고침을 방지합니다.
          // 폼 요소의 name 속성을 이용하여 입력된 제목과 본문 내용을 가져옵니다.
          const title = event.target.title.value;
          const body = event.target.body.value;
          props.onCreate(title, body); // 부모 컴포넌트로부터 전달받은 생성 함수를 호출합니다.
        }}
      >
        <p>
          <input type="text" name="title" placeholder="title" /> {/* 제목 입력 필드입니다. */}
        </p>
        <p>
          <textarea name="body" placeholder="body"></textarea> {/* 본문 내용 입력 필드입니다. */}
        </p>
        <p>
          <input type="submit" value="Create"></input> {/* 제출 버튼입니다. */}
        </p>
      </form>
    </article>
  );
}

/**
 * Update 컴포넌트: 기존 게시글을 수정하기 위한 폼을 렌더링합니다.
 * @param {object} props - 컴포넌트에 전달되는 속성 객체입니다.
 * @param {string} props.title - 수정할 게시글의 초기 제목입니다.
 * @param {string} props.body - 수정할 게시글의 초기 본문입니다.
 * @param {function(string, string): void} props.onUpdate - 폼 제출 시 호출될 콜백 함수입니다. 수정된 제목과 본문 내용을 인자로 전달합니다.
 */
function Update(props) {
  // 입력 필드의 상태를 관리하기 위해 useState 훅을 사용하며, props로부터 초기값을 받습니다.
  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);

  return (
    <article>
      <h2>Update</h2> {/* 'Update' 제목을 표시합니다. */}
      {/* 폼 제출 시 기본 동작을 방지하고, 입력된 제목과 본문으로 onUpdate 콜백 함수를 호출합니다. */}
      <form
        onSubmit={(event) => {
          event.preventDefault(); // 폼 제출 시 페이지 새로고침을 방지합니다.
          // 폼 요소의 name 속성을 이용하여 입력된 제목과 본문 내용을 가져옵니다.
          // (여기서는 value 속성과 onChange 핸들러를 통해 관리되는 상태인 title, body를 직접 사용해야 합니다.)
          props.onUpdate(title, body); // 부모 컴포넌트로부터 전달받은 업데이트 함수를 호출합니다.
        }}
      >
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={title} // 상태 변수 title을 입력 필드의 값으로 설정합니다. (Controlled Component)
            onChange={(event) => {
              setTitle(event.target.value); // 입력 필드 값 변경 시 title 상태를 업데이트합니다.
            }}
          />
        </p>
        <p>
          <textarea
            name="body"
            placeholder="body"
            value={body} // 상태 변수 body를 입력 필드의 값으로 설정합니다. (Controlled Component)
            onChange={(event) => {
              setBody(event.target.value); // 입력 필드 값 변경 시 body 상태를 업데이트합니다.
            }}
          ></textarea>
        </p>
        <p>
          <input type="submit" value="Update"></input> {/* 제출 버튼입니다. */}
        </p>
      </form>
    </article>
  );
}

/**
 * App 컴포넌트: 애플리케이션의 메인 엔트리 포인트입니다.
 * 전체 UI를 구성하고 상태를 관리합니다.
 */
export default function App() {
  // 현재 애플리케이션의 모드를 관리하는 상태 변수입니다 (예: "WELCOME", "READ", "CREATE", "UPDATE").
  const [mode, setMode] = useState("WELCOME");
  // 현재 선택된 topic의 ID를 관리하는 상태 변수입니다.
  const [id, setId] = useState(null);
  // 새로운 topic에 할당될 다음 ID를 관리하는 상태 변수입니다.
  const [nextId, setNextId] = useState(4);
  // 내비게이션 및 콘텐츠를 구성하는 데 사용될 주제(topic) 데이터 배열입니다.
  // useState를 사용하여 상태로 관리되므로, 데이터 추가/수정 시 리렌더링이 발생합니다.
  const [topics, setTopics] = useState([
    { id: 1, title: "html", body: "html is ..." },
    { id: 2, title: "css", body: "css is ..." },
    { id: 3, title: "javascript", body: "javascript is ..." },
  ]);

  let content = null; // 현재 모드에 따라 렌더링될 메인 콘텐츠 컴포넌트를 저장할 변수입니다.
  let contextControl = null; // 특정 모드에서 추가적으로 표시될 제어 링크(예: Update)를 저장할 변수입니다.

  // 현재 mode 상태에 따라 다른 콘텐츠를 렌더링합니다.
  if (mode === "WELCOME") {
    // mode가 "WELCOME"일 경우, 환영 메시지를 담은 Article 컴포넌트를 렌더링합니다.
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    // mode가 "READ"일 경우, 선택된 topic의 내용을 담은 Article 컴포넌트를 렌더링합니다.
    let title,
      body = null;
    // topics 배열을 순회하여 현재 선택된 id와 일치하는 topic을 찾습니다.
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title; // 해당 topic의 제목을 가져옵니다.
        body = topics[i].body; // 해당 topic의 본문 내용을 가져옵니다.
        break; // 찾았으면 더 이상 순회할 필요가 없습니다.
      }
    }
    content = <Article title={title} body={body}></Article>; // 찾은 topic 내용으로 Article 컴포넌트를 렌더링합니다.

    // READ 모드일 때만 'Update' 링크를 표시합니다.
    contextControl = (
      <li>
        <a
          href={"/update/" + id} // 수정할 항목의 ID를 URL에 포함합니다.
          onClick={(event) => {
            event.preventDefault(); // 기본 링크 동작을 방지합니다.
            setMode("UPDATE"); // 모드를 "UPDATE"로 변경하여 수정 폼을 표시합니다.
          }}
        >
          Update
        </a>
      </li>
    );
  } else if (mode === "CREATE") {
    // mode가 "CREATE"일 경우, Create 컴포넌트를 렌더링하여 새 글 작성을 허용합니다.
    content = (
      <Create
        onCreate={(_title, _body) => {
          // Create 컴포넌트에서 전달받은 제목과 본문으로 새로운 topic 객체를 생성합니다.
          const newTopic = { id: nextId, title: _title, body: _body };
          // 기존 topics 배열을 복사하여 불변성을 유지하고 새 topic을 추가합니다.
          const newTopics = [...topics];
          newTopics.push(newTopic);
          setTopics(newTopics); // 업데이트된 topics 배열로 상태를 변경합니다.
          setMode("READ"); // 새 글 생성 후 "READ" 모드로 전환합니다.
          setId(nextId); // 새로 생성된 글의 ID를 현재 선택된 ID로 설정합니다.
          setNextId(nextId + 1); // 다음 글 작성을 위해 nextId를 1 증가시킵니다.
        }}
      ></Create>
    );
  } else if (mode === "UPDATE") {
    // mode가 "UPDATE"일 경우, Update 컴포넌트를 렌더링하여 기존 글 수정을 허용합니다.
    let title,
      body = null;
    // topics 배열을 순회하여 현재 선택된 id와 일치하는 topic을 찾습니다.
    for (let i = 0; i < topics.length; i++) {
      // console.log(topics[i].id, id); // 디버깅 용도
      if (topics[i].id === id) {
        title = topics[i].title; // 해당 topic의 현재 제목을 가져옵니다.
        body = topics[i].body; // 해당 topic의 현재 본문 내용을 가져옵니다.
        break; // 찾았으면 더 이상 순회할 필요가 없습니다.
      }
    }
    content = (
      <Update
        title={title} // Update 컴포넌트에 현재 글의 제목을 props로 전달합니다.
        body={body} // Update 컴포넌트에 현재 글의 본문을 props로 전달합니다.
        onUpdate={(_title, _body) => {
          // console.log(_title, _body); // 디버깅 용도
          const newTopics = [...topics]; // 기존 topics 배열을 복사하여 불변성을 유지합니다.
          const updatedTopic = { id: id, title: _title, body: _body }; // 수정된 내용으로 새 topic 객체를 생성합니다.
          // 복사된 배열에서 현재 ID와 일치하는 topic을 찾아 수정된 topic으로 교체합니다.
          for (let i = 0; i < newTopics.length; i++) {
            if (newTopics[i].id === id) {
              newTopics[i] = updatedTopic;
              break; // 업데이트 후 순회를 중단합니다.
            }
          }
          setTopics(newTopics); // 업데이트된 topics 배열로 상태를 변경합니다.
          setMode("READ"); // 수정 완료 후 "READ" 모드로 전환합니다.
        }}
      ></Update>
    );
  }

  return (
    <div className="App">
      {/* Header 컴포넌트를 렌더링하고, 제목은 "WEB"으로 설정합니다. */}
      {/* onChangeMode 프롭스에 콜백 함수를 전달하여 클릭 시 mode를 "WELCOME"으로 변경하도록 합니다. */}
      <Header
        title="WEB"
        onChangeMode={() => {
          setMode("WELCOME");
        }}
      ></Header>
      {/* Nav 컴포넌트를 렌더링하고, topics 데이터를 전달합니다. */}
      {/* onChangeMode 프롭스에 콜백 함수를 전달하여 클릭 시 mode를 "READ"로, id를 선택된 topic의 ID로 변경하도록 합니다. */}
      <Nav
        topics={topics}
        onChangeMode={(_id) => {
          setMode("READ");
          setId(_id);
        }}
      ></Nav>
      {/* 현재 mode에 따라 동적으로 결정된 content 컴포넌트를 렌더링합니다. */}
      {content}
      {/* 콘텐츠 제어 링크 목록 (Create, Update 등) */}
      <ul>
        {/* "Create" 링크: 클릭 시 기본 동작을 막고 mode를 "CREATE"로 변경하여 글쓰기 폼을 표시합니다. */}
        <li>
          <a
            href="/create"
            onClick={(event) => {
              event.preventDefault(); // 기본 링크 동작(페이지 이동)을 방지합니다.
              setMode("CREATE"); // 모드를 "CREATE"로 설정하여 새 글 작성 폼을 표시합니다.
            }}
          >
            Create
          </a>
        </li>
        {/* READ 모드에서만 'Update' 링크를 표시합니다. */}
        {contextControl}
      </ul>
    </div>
  );
}

'생활코딩! React 리액트 프로그래밍' 카테고리의 다른 글

egoing - React Router DOM - stackblitz ver  (0) 2025.09.05
egoing - React - delete  (0) 2025.06.25
egoing - React - create  (0) 2025.06.23
egoing - React - state  (0) 2025.06.23
egoing - React - event  (0) 2025.06.21