이번 5장에서는 CRUD 중 Update의 기능에 대해 다루어 보려고 한다.
egoing님의 말을 빌리자면, Update는 Create와 Read의 기능을 조금씩 합쳐(하이브리드) 구현한다. 코드는 지난 정리 4강에서 최종 완성된 코드를 사용해, 계속해서 편집할 것이다. (최종 코드 전문은 React 기초 정리 4강 말단에 있다.)
시작해 보자.
Update 기능 구현
요구사항
우선, Update 기능을 위해 구현해야 할 것들은 다음과 같다.
- 유저가 글을 읽는 상황(
SUB
모드)에서, Update 버튼이 Create 아래에 표시된다. 이 버튼을 누르면 유저가 읽고 있던 항목의 정보를 담은 title, body 입력(수정)창과 그 아래 Update 버튼이 표시된 Update 화면으로 본문이 변경된다.
내용은 다음과 같다. (value 내 title, body는Update
컴포넌트의 파라미터이다.)<h2>update</h2> <form> <p><input type="text" name="title" placeholder="title" value={title}/><p> <p><textarea name="body" placeholder="body" value={body}></textarea></p> <p><input type="submit" value="Update"></input></p> </form>
- 유저가 title, body의 글을 수정하고, Update 버튼을 누르게 되면 수정된 항목의 읽기 화면(
SUB
모드)이 표시된다.
요구사항과 뷰는 Create와 크게 다르지 않아 간단하다. 그렇다면, 이제 코드를 작성하자.
구현
우선, Update 상태로 넘어가기 위한 a
태그를 App()
의 return 부분에 작성하자.
하지만 이렇게 작성하면, a
태그는 인라인 요소이므로, 다음과 같이 기존 Create a
태그의 바로 옆에 보이게 된다.
그러므로 li
태그를 사용해 Create와 Update a
태그들이 각각 다른 줄에 표시되도록 하고, 이 전체를 ul
태그로 감싸 목록화하자.
그러나 이렇게 하면 mode
가 MAIN
인 경우에도 Update 태그가 나타나게 된다. 따라서 이를 contextControl
이라는 이름의 변수로 지정하여, mode
가 SUB
일 때만 Update 요소가 보이게 하자.
이를 구현하기 위해서, App()
의 지역 변수로 const contextControl = null;
을 작성했고, if문에서 SUB
모드일 경우 실행되는 코드 블럭 안에, contextControl
값에 Update 요소을 대입하는 구문을 추가했다. 그리고 App()
의 return 값 하단에 중괄호 문법을 사용하여 contextControl
이 표시되도록 하였다.
또 추가로, 주소 형식을 지키기 위해 contextControl
의 href 값에 id
를 할당하도록 하였다.
이제, 본격적으로 함수를 사용하여 Update 기능을 구현하자. 우선 수정해야 할 부분은 mode
변경을 수행하는 contextControl
변수에 담긴 a
태그로, onClick
을 사용하여 함수를 작성해야 한다.
새로고침을 방지하고, mode
를 UPDATE
로 변경하는 구문을 작성해 주었다.
mode
를 UPDATE
로 변경하는 구문을 작성하였으니, 이제 if문에 mode
가 UPDATE
면 실행될 구문들을 작성해 주자. 본문을 담는 content
변수의 내용은 이후 작성될 Update
컴포넌트로 한다.
이제 Update
컴포넌트를 새로 작성하자.
자, 여기에서 우리는 Update
의 구현이 Create와 Read의 하이브리드라는 이야기가왜 이 글의 시작에 있었는지 알 수 있다. Update
의 기능에 대해 잘 생각해 보자. 분해해 보면, 다음과 같다.
- 선택된
topics
의 원소의 title, body 값을 읽어옴. - (1)의 값을
Create
와 동일한 폼의 입력창에 미리 입력해 둠. - (2)의 값으로 기존
topics
의 원소 값을 수정함.
여기서, 1번은 이전에 구현한 Sub
의 기능을, 2번은 Create
의 기능을 조금만 수정하면 된다. 따라서, 다음 코드는 이들을 복사한 뒤 조금만을 수정한 코드이다.
if문 내부의 구조는 mode
가 SUB
일 때의 구조를, 새로 작성한 Update
컴포넌트 내부는 Create
컴포넌트 내부를 살짝 변형했다.
하지만 이렇게 하면 Update 시 topics
원소의 내용은 잘 표시되지만, 글이 수정되지 않는다. (아예 text field에 글자 입력, 삭제가 되지 않는다.)
그 이유는 Update
컴포넌트 내에서, input
과 textarea
의 value
속성을 props로 받아온 값으로 설정한 데에 있다.
Props는 외부자(App
)가 내부자(Update
컴포넌트)로 전달하는 값이므로, 컴포넌트 내부에서 마음대로 변경할 권한이 없다. 따라서, 이를 수정할 수 있도록 하기 위해서는 title
과 body
를 State로 만들어 주어야 한다.
State를 두개 생성하여 Props로 넘어온 title
, body
를 담아 주었고, 아래 태그들의 value
속성의 값도 해당 State로 변경했다.
그러나 이렇게 해도 글이 수정되지 않는데, 이는 여전히 글 수정 시 state가 변경되지 않고 있기 때문이다.
따라서 React에서는 수정 시마다 작동하는(HTML에서와 다르게 동작) onChange
함수를 사용하여, 수정 시마다 setState
함수가 호출되도록 해야 한다.
다음과 같이 수정하면 글이 잘 수정된다. 마침 위에서 바꾸지 않았던, submit 버튼의 이름도 'Create'가 아닌 'Update'로 바꿔 주었다.
이제 Update
컴포넌트의 내부 구현은 완성했고, submit 버튼을 누르면 호출되는 props의 onUpdate
함수를 구현할 차례이다. 이 함수를 실행하면, 항목에 title과 body가 잘 저장되어야 한다.
이와 같이 코드를 작성하면, Create에서 했던 것과 같이 Shallow Copy를 사용해 배열을 수정(id
는 SUB
상태에서 set된 그대로 가져오되, topics
의 id
시작이 1부터이므로 -1을 하여 수정했다.), mode
는 다시 SUB
로 되돌려 놓았다.
여기까지 코드를 작성하면, 요구사항대로 잘 작동하는 것을 볼 수 있다. 다음은 코드 전문이다.
import './App.css';
import {useState} from 'react';
function Article(props){
return (
<article>
<h2>{props.title}</h2>
{props.body}
</article>
);
}
function Header(props){
return (
<header>
<h1><a href="/" onClick={(event)=>{
event.preventDefault();
props.onChangeMode();
}}>{props.title}</a></h1>
</header>
);
}
function Nav(props){
const lis = props.topics.map((topic) => {
return (
<li key={topic.id}>
<a id={topic.id} href={'/read/' + topic.id} onClick={event => {
event.preventDefault();
props.onChangeMode(Number(event.target.id));
}}>{topic.title}</a>
</li>
);
});
return (
<nav>
<ol>
{lis}
</ol>
</nav>
);
}
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'></input></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);
}}></input></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>
);
}
function App() {
const [mode, setMode] = useState('MAIN');
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 === 'MAIN') {
content = <Article title="Welcome" body="Hello, WEB"></Article>;
} else if (mode === 'SUB') {
let title, body = null;
topics.forEach((topic)=> {
if (topic.id === id) {
title = topic.title;
body = topic.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 newTopics = [...topics];
const newTopic = {id: nextId, title: title, body: body};
newTopics.push(newTopic);
setTopics(newTopics);
setMode('SUB');
setId(nextId);
setNextID(nextId + 1);
}}></Create>;
} else if (mode === 'UPDATE') {
let title, body = null;
topics.forEach((topic)=> {
if (topic.id === id) {
title = topic.title;
body = topic.body;
}
});
content = <Update title={title} body={body} onUpdate={(title, body) => {
const newTopics = [...topics];
const updatedTopic = {id: id, title: title, body: body};
newTopics.forEach((topic) => {
if (topic.id === id) {
newTopics[id - 1] = updatedTopic;
}
});
setTopics(newTopics);
setMode('SUB');
}}></Update>
}
return (
<div>
<Header title="WEB" onChangeMode={()=>{
setMode('MAIN');
}}></Header>
<Nav topics={ topics } onChangeMode={(selectedID)=>{
setMode('SUB');
setId(selectedID);
}}></Nav>
{ content }
<ul>
<li>
<a href='/create' onClick={(event) => {
event.preventDefault();
setMode('CREATE');
}}>Create</a>
</li>
{ contextControl }
</ul>
</div>
);
}
export default App;
이렇게 CRUD 중 Update 기능까지 구현해 보았다. 이제 남은 것은 Delete 기능인데, React 기초 정리 시리즈의 마지막이 될 다음 정리 6장에서 이를 다루도록 하겠다.
'프레임워크 · 라이브러리 > React' 카테고리의 다른 글
React 기초 정리 - 6장 (Delete 기능 구현, 마무리) (0) | 2024.04.12 |
---|---|
React 기초 정리 - 4장 (CRUD, Create 기능 구현 + 객체 State의 변경) (0) | 2024.04.12 |
React 기초 정리 - 3장 (이벤트, State) (0) | 2024.04.12 |
React 기초 정리 - 2장 (배포, 컴포넌트, Props + 중괄호 문법) (0) | 2024.04.12 |
React 기초 정리 - 1장 (React란, 개발 환경 설정, 폴더 구조) (0) | 2024.04.12 |