

지출 항목 쓰고 비용 작성 후 제출 버튼 누르면
"아이템이 생성되었습니다." 위에 뜨면서 리스트 항목 추가됨
🖋수정 🗑삭제까지


App을 export 해줘야 함
default로 export(수출)할 때➡이름 아무렇게나 지정해줘도 됨🙆♀️
default 아닌 경우➡이름 통일 해야됨✅







⬆ 생략 시 위처럼 사용할 것😎
1 ) 설치하기
npm install react-icons --save

import { MdDelete, MdEdit } from 'react-icons/md'
<MdEdit />
<MdDelete />





👉 에러 : 유니크한 key prop 있어야 함🔑
이젠 에러 Byeee👋
리액트에서 요소의 리스트를 나열할 땐 Key 넣어줘야 함🔑
키는 리액트가 변경, 추가 또는 제거된 항목을 식별하는 데 도움 됨
요소에 안정적인 ID 부여하려면 배열 내부의 요소에 키를 제공해야함🙆♀️
이전 돔 & 현재 돔 두 개의 돔을 비교(DIFF) 해서 다른 부분만 실제 돔에 적용✨
도움을 줄 수 있는 부분 중 하나가 key 프랍 넣어주는 것🔥
만약 리스트 맨 뒤에 추가 했다고 하면 리액트는 이정돈 알 수 있음<button
onClick={() => console.log(`${this.props.expense.id} clicked`)}>
<MdDelete />
</button>
클릭하면 콘솔에 잘 나옴// 어떠한 걸 지웠는지 알아야 하기에 id로 지정
handleDelete = (id) => {
console.log(id);
// 같지않은 객체만 넣어주면 됨
const newExpenses = this.initialExpenses.filter(expense =>
expense.id !== id
)
// 렌트비 삭제 버튼 누르면 콘솔에 이 객체 부분만 사라짐
console.log(newExpenses);
}
// 👩🦱App(부모) -> 👩ExpenseList(자식)으로 데이터 내려주기
<ExpenseList
handleDelete={this.handleDelete}
/>
⬇
// ExpenseList-> ExpenseItem으로 다시 한번 더 데이터 내려주기 props
<ExpenseItem
handleDelete={this.props.handleDelete}
/>
<button
onClick={() =>
this.props.handleDelete(this.props.expense.id)
}>
<MdDelete />
</button>
아하 함수도 props로 내릴 수 있겠구나 ❗❗
렌트비를 삭제 버튼 누르면 콘솔에 그 객체 부분만 없어짐 ( 화면에선 변화 없음😕 )
👉 클래스에서 state 만들기 ( 🧠컴포넌트 안에서 기억하고 싶을 때 사용🙆♀️ )constructor (props) {
super(props);
this.state = {
expenses: [
{ id: 1, charge: '렌트비', amount: 1600 },
{ id: 2, charge: '교통비', amount: 400 },
{ id: 3, charge: '식비', amount: 1200 },
]
}
}
// 어떠한 걸 지웠는지 알아야 하기에 id로 지정
handleDelete = (id) => {
console.log(id);
// 같지않은 객체만 넣어주면 됨
const newExpenses = this.initialExpenses.filter(expense =>
expense.id !== id
)
// 렌트비 삭제 버튼 누르면 콘솔에 이 객체 부분만 사라짐
console.log(newExpenses);
}
⬇
const newExpenses = this.state.expenses.filter(expense =>
expense.id !== id
)
<ExpenseList
initialExpenses={this.initialExpenses}
handleDelete={this.handleDelete}
/>
⬇
<ExpenseList
initialExpenses={this.state.expenses}
handleDelete={this.handleDelete}
/>
원래 있던 이 데이터에서 하나의 데이터를 삭제 했을 때는 ⬇
없앤 거를 state에 expenses의 값으로 업데이트 해줘야 함
여기서 state 값을 변경해 주면 됨🙆♀️✅// 어떠한 걸 지웠는지 알아야 하기에 id로 지정
handleDelete = (id) => {
console.log(id);
// 'newExpenses'라는 새 배열을 생성
// this.state.expenses: 현재 상태(state)에서 'expenses' 배열을 가져옴
// .filter(): 배열에서 특정 조건에 맞는 항목만 남기고 새로운 배열을 반환
const newExpenses = this.state.expenses.filter(expense =>
/* 각 항목인 'expense'의 'id'가 삭제하고자 하는
'id'와 같지 않은 경우(true)만 필터링 */
expense.id !== id // 만약 id 2를 삭제했다면 2를 제외한 나머지 배열 반환
// 결과적으로 'id'가 일치하는 항목을 제외한 새로운 배열이 생성됨
);
// 이 부분에 state 업데이트 해줘야 함
this.setState({
expenses: newExpenses
})
// 렌트비 삭제 버튼 누르면 콘솔에 이 객체 부분만 사라짐
console.log(newExpenses);
}
새로고침하고 삭제 버튼 누르면
삭제 버튼 눌렀을 때 state가 변경이 되고 state가 변경이 되면import React , { Component } from 'react'
export default class ExpenseForm extends Component {
render() {
return (
<form>
<div>
/* htmlFor: 연결된 input 요소의 id를 가리킴
여기선 'change'라는 id를 가진 input과 연결됨
htmlFor='username'으로 설정된 label 클릭하면
id="username" 가진 input에 자동으로 포커스 가게됨 */
<label htmlFor='change'>
지출 항목
</label>
<input
type='text'
id='change'
name='change'
placeholder='예 ) 렌트비'
/>
</div>
<div>
<label htmlFor='amount'>
비용
</label>
<input
type='number'
id='amount'
name='amount'
placeholder='예 ) 100'
/>
</div>
<button
type='submit'
>
제출
</button>
</form>
)
}
}
⬇
rafcimport React from 'react'
export const ExpenseForm = () => {
return (
<div>ExpenseForm</div>
)
}
import React from 'react'
export const ExpenseForm = () => {
return (
<form>
<div>
<label htmlFor='change'>
지출 항목
</label>
<input
type='text'
id='change'
name='change'
placeholder='예 ) 렌트비'
/>
</div>
<div>
<label htmlFor='amount'>
비용
</label>
<input
type='number'
id='amount'
name='amount'
placeholder='예 ) 100'
/>
</div>
<button
type='submit'
>
제출
</button>
</form>
)
}
import React , { Component } from 'react'
import { MdDelete, MdEdit } from 'react-icons/md'
export default class ExpenseItem extends Component {
render() {
return (
<li>
<div>
<span>
{this.props.expense.charge}
</span>
<span>
{this.props.expense.amount}
</span>
</div>
<div>
<button>
<MdEdit />
</button>
<button
onClick={() =>
this.props.handleDelete(this.props.expense.id)
}>
<MdDelete />
</button>
</div>
</li>
)
}
}
⬇
import React from 'react'
const ExpenseItem = () => {
return (
<li>
<div>
<span>
{this.props.expense.charge}
</span>
<span>
{this.props.expense.amount}
</span>
</div>
<div>
<button>
<MdEdit />
</button>
<button
onClick={() =>
this.props.handleDelete(this.props.expense.id)
}>
<MdDelete />
</button>
</div>
</li>
)
}
export default ExpenseItem
함수형에선 props 가져오는 방식을 this.으로 하지 않음🙅♀️import React from 'react'
const ExpenseItem = (props) => {
return (
<li>
<div>
<span>
{props.expense.charge}
</span>
<span>
{props.expense.amount}
</span>
</div>
<div>
<button>
<MdEdit />
</button>
<button
onClick={() =>
props.handleDelete(props.expense.id)
}>
<MdDelete />
</button>
</div>
</li>
)
}
export default ExpenseItem
import React , { useState } from "react";
import ExpenseForm from "./components/ExpenseForm";
import ExpenseList from "./components/ExpenseList";
const App = () => {
const [expenses, setExpenses] = useState([
{ id: 1, charge: '렌트비', amount: 1600 },
{ id: 2, charge: '교통비', amount: 400 },
{ id: 3, charge: '식비', amount: 1200 },
])
// 함수형 안에 있으니까 const 넣어주기
const handleDelete = (id) => {
const newExpenses = expenses.filter(expense =>
expense.id !== id
)
setExpenses(newExpenses);
}
return (
<main>
<h1>예산 계산기</h1>
<div style={{
width: '100%',
backgroundColor: 'white',
//'rem' 단위: <html> 요소의 기본 글꼴 크기 기준으로 한 상대적인 크기
// 1rem == 16px
padding: '1rem'
}}>
<ExpenseForm />
</div>
<div style={{
width: '100%',
backgroundColor: 'white',
padding: '1rem'
}}>
<ExpenseList
initialExpenses={expenses}
handleDelete={handleDelete}
/>
</div>
<div>
<p>
총지출:
<span>원</span>
</p>
</div>
</main>
)
}
export default App
state
state 초기값
state 업데이트import React from 'react'
import ExpenseItem from './ExpenseItem'
const ExpenseList = (props) => {
return (
<>
<ul>
{
props.initialExpenses.map(expense => {
return (
<ExpenseItem
expense={expense}
key={expense.id}
handleDelete={props.handleDelete}
/>
)
})
}
</ul>
<button>
목록 지우기
</button>
</>
)
}
export default ExpenseList
import React from 'react'
import ExpenseItem from './ExpenseItem'
/* ExpenseList 컴포넌트: props로 전달받은 initialExpenses와
handleDelete를 구조 분해 할당으로 받아옴 */
/* 구조 분해 할당 하면 props.initialExpenses,
props.handleDelete 대신 더 짧고 직관적인 변수명 사용 가능 */
const ExpenseList = ({ initialExpenses, handleDelete }) => {
return (
<>
<ul>
{
/* initialExpenses 배열을 map 메서드로 순회하여
각 expense 항목을 화면에 렌더링 */
/* 여기서 각 expense는 개별 ExpenseItem 컴포넌트로 렌더링되며
key 값으로 고유한 expense.id를 사용 */
initialExpenses.map(expense => {
return (
<ExpenseItem
// 개별 expense 항목을 ExpenseItem 컴포넌트에 전달
expense={expense}
// key 속성은 각 항목을 식별하기 위해 필요함
key={expense.id}
/* handleDelete 함수를 ExpenseItem에 전달하여
항목 삭제를 처리할 수 있게 함 */
handleDelete={handleDelete}
/>
)
})
}
</ul>
<button>
목록 지우기
</button>
</>
)
}
export default ExpenseList
지출 항목 & 비용 입력하고 제출 버튼 누르면
카드비 라는 이 값을 App에서 기억을 하고 있어야 한다는 말🧠const [charge, setCharge] = useState('');
// 비용 input 부분
const [amount, setAmount] = useState(0);
// 값 내려주기 props
<ExpenseForm charge={charge} />
⬇
// ...
const ExpenseForm = ({charge}) => {
return (
// ...
<input
type='text'
id='change'
name='change'
placeholder='예 ) 렌트비'
value={charge}
/>
</div>
// ...
빈 문자열로 초기 값을 줘서 input에 값을 입력해도 아무런 변화❌😕// ...
// 함수 만들기
const handleCharge = () => {
}
// ...
return (
<main>
// ...
<ExpenseForm
charge={charge}
// 함수 내려주기
handleCharge={handleCharge}
/>
// ...
⬇
import React from 'react'
// handleCharge 받아주기
const ExpenseForm = ({ charge, handleCharge }) => {
return (
<form>
// ...
<input
type='text'
id='change'
name='change'
placeholder='예 ) 렌트비'
value={charge}
// 이렇게 넣어주면 됨
onChange={handleCharge}
/>
// ...
⬇
// ...
// 함수 만들기
const handleCharge = (e) => {
setCharge(e.target.value);
}
// ...
이젠 지출 항목 input 타이핑 잘 됨👍// ...
// 비용을 위해 함수 만들기
const handleAmount = (e) => {
}
// ...
return (
<main>
// ...
<ExpenseForm
charge={charge}
handleCharge={handleCharge}
// 넣어주기
amount={amount}
handleAmount={handleAmount}
/>
// ...
⬇
import React from 'react'
// amount, handleAmount 넣어주기
const ExpenseForm = ({ charge, handleCharge,
amount, handleAmount }) => {
return (
<form>
// ...
<label htmlFor='amount'>
비용
</label>
<input
type='number'
id='amount'
name='amount'
placeholder='예 ) 100'
// 넣어주기
value={amount}
onChange={handleAmount}
/>
// ...
⬇
import React , { useState } from "react";
import ExpenseForm from "./components/ExpenseForm";
import ExpenseList from "./components/ExpenseList";
const App = () => {
const [expenses, setExpenses] = useState([
{ id: 1, charge: '렌트비', amount: 1600 },
{ id: 2, charge: '교통비', amount: 400 },
{ id: 3, charge: '식비', amount: 1200 },
])
const handleAmount = (e) => {
setAmount
}
// 함수형 안에 있으니까 const 넣어주기
const handleDelete = (id) => {
const newExpenses = expenses.filter(expense =>
expense.id !== id
)
setExpenses(newExpenses);
}
return (
<main>
<h1>예산 계산기</h1>
<div style={{
width: '100%',
backgroundColor: 'white',
//'rem' 단위: <html> 요소의 기본 글꼴 크기 기준으로 한 상대적인 크기
// 1rem == 16px
padding: '1rem'
}}>
<ExpenseForm />
</div>
<div style={{
width: '100%',
backgroundColor: 'white',
padding: '1rem'
}}>
<ExpenseList
initialExpenses={expenses}
handleDelete={handleDelete}
/>
</div>
<div>
<p>
총지출:
<span>원</span>
</p>
</div>
</main>
)
}
export default App
setAmount(e.target.value)로 하면 비용 input 타이핑 되기는 한데
원래 타입은 number인데 string으로 변경한 것😬const handleAmount = (e) => {
console.log(typeof e.target.value);
setAmount(Number(e.target.value));
}
// number
const handleAmount = (e) => {
// console.log(typeof e.target.valueAsNumber);
setAmount(e.target.valueAsNumber);
}

state가 각각의 값들을 기억하고 있음🧠import React , { Component } from 'react'
export default class ExpenseForm extends Component {
render() {
return (
// 제출 버튼 눌렀을 때 함수 호출 하는 것 등록
// 만든 함수 가져오기 -> handleSubmit
<form onSubmit={handleSubmit}>
<div>
<label htmlFor='change'>
지출 항목
</label>
<input
type='text'
id='change'
name='change'
placeholder='예 ) 렌트비'
/>
</div>
<div>
<label htmlFor='amount'>
비용
</label>
<input
type='number'
id='amount'
name='amount'
placeholder='예 ) 100'
/>
</div>
<button
// submit을 지정해줘서
type='submit'
>
제출
</button>
</form>
)
}
}
// 폼 제출 시 호출되는 함수
const handleSubmit = e => {
// preventDefault 사용하여 브라우저의 기본 폼 제출 동작을 방지
// 이 동작이 없으면 폼이 실제로 제출되어 페이지가 새로고침
e.preventDefault();
// 유효성 검사
// 지출 항목이랑 비용 입력 안했는데
// 제출 버튼 눌렀어도 전송 안 되게 하기
// 지출 항목이 빈문자열 아니거나 비용이 0보다 크면
if (charge !== "" && amount > 0) {
// 객체 만들기
const newExpenses = {
// 유니크한 id 사용
id: cryto.randomUUID(),
charge,
amount
}
// state는 항상 불변성 지켜줘야함
const newExpenses =
[...expenses, newExpenses]
// state 업데이트 해주기
setExpenses(newExpenses);
// 작성한 것들 초기화 해주기
setCharge("");
setAmount(0);
} else { // 아무것도 작성안하고 제출 버튼 누를경우
console.error("error");
}
}
// ...
<ExpenseForm
charge={charge}
handleCharge={handleCharge}
amount={amount}
handleAmount={handleAmount}
// 만든 함수 내려주기
handleSubmit={handleSubmit}
/>
//...
콘솔에 crypto.randomUUID 입력하면 유니크한 값이 랜덤으로 나옴
지출 항목&비용 작성하고 제출 버튼 누르면 리스트 추가 됨 ( 쨔잔😮 )
amount 각각 다 더해줘서 총지출 나오게 하면 됨// ...
const App = () => {
const [expenses, setExpenses] = useState([
{ id: 1, charge: '렌트비', amount: 1600 },
{ id: 2, charge: '교통비', amount: 400 },
{ id: 3, charge: '식비', amount: 1200 },
])
// ...
return (
<main>
<h1>예산 계산기</h1>
// ...
<div>
<p>
총지출:
<span>
// 초기값은 0 : 0부터 계속 더해주면 되니까
// acc : 누적 값
// curr : 배열 하나하나의 값
{expenses.reduce((acc, curr) => {
return (acc += curr.amount)
}, 0)}
원</span>
</p>
</div>
</main>
)
}
export default App

수정 버튼 누르면 렌트비 2000 이 값들을 위로 올려주는 방식
값을 변경 후 수정 버튼 누르면 값이 수정됨
// ...
const App = () => {
// 1. 수정 안하고 있을 때 수정하고 있을 때를 구별해줄 수 있는 state 만들기
// false로 지정 : 수정 안하고 있으니까
const [isEditing, setIsEditing] = useState(false);
const [expenses, setExpenses] = useState([
{ id: 1, charge: '렌트비', amount: 1600 },
{ id: 2, charge: '교통비', amount: 400 },
{ id: 3, charge: '식비', amount: 1200 },
])
// 2. 수정 클릭했을 때 호출할 함수 만들기
// 어떤 걸 수정했는지 알기 위해 id 받아오기
// 눌렀을 때 호출해야 하니까 handleEdit 내려주기
const handleEdit = (id) => {
}
const handleAmount = (e) => {
setAmount
}
// 함수형 안에 있으니까 const 넣어주기
const handleDelete = (id) => {
const newExpenses = expenses.filter(expense =>
expense.id !== id
)
setExpenses(newExpenses);
}
return (
<main>
<h1>예산 계산기</h1>
// ...
<ExpenseList
initialExpenses={expenses}
handleDelete={handleDelete}
// 3. handleEdit 함수 만든거 내려주기
handleEdit={handleEdit}
/>
</div>
<div>
// ...
</div>
</main>
)
}
export default App
⬇
// ...
const ExpenseList = ({
initialExpenses,
handleDelete,
// 4. handleEdit 구조 분해 할당 하기
handleEdit
}) => {
return (
<>
<ul>
{
initialExpenses.map(expense => {
return (
<ExpenseItem
expense={expense}
key={expense.id}
handleDelete={handleDelete}
// 5. handleEdit 내려주기
handleEdit={handleEdit}
/>
// ...
⬇
// ...
// 7. expense, handleEdit, handleDelete 구조분해 할당하고
// props. 다 지워주기
const ExpenseItem = ({ expense, handleEdit, handleDelete }) => {
return (
<li>
// ...
<MdEdit
// 6.
onClick={() => handleEdit(expense.id)}
/>
</button>
<button onClick={() => handleDelete(expense.id)}>
<MdDelete />
</button>
// ...
⬇
// ...
const App = () => {
const [isEditing, setIsEditing] = useState(false);
// ...
const handleEdit = (id) => {
// 7. 수정 버튼 누르면 수정된 후니까 true 변경하기
setIsEditing(true);
}
// ...
return (
<main>
// ...
</main>
)
}
export default App
🖍클릭 했을 때 handleEdit 함수가 호출📢🙆♀️
🖍 수정 버튼 클릭한 후면 수정된 이후니까 true로 바꿔줘야 함❗❗// ...
const App = () => {
const [isEditing, setIsEditing] = useState(false);
// 8. 객체의 id를 넣어줄 state 생성하기
const [id, setId] = useState('');
// ...
const handleEdit = (id) => {
setIsEditing(true);
// 9. id 넣어주기
setId(id);
}
// ...
return (
<main>
// ...
</main>
)
}
export default App

// ...
const App = () => {
// ...
const handleEdit = (id) => {
// 9. charge, amount state 변경
// 1 ) 값을 가져오기
// 수정 버튼 클릭한 id와 같으면 그 객체를 리턴하게 됨-> find
const expense = expenses.find(item => item.id === id);
const { charge, amount } = expense;
// 2 ) state 값 변경하기
setCharge(charge);
setAmount(amount);
// ...
}
// ...



// ...
return (
<form onSubmit={handleSubmit}>
<div>
// ...
</div>
<button
// submit을 지정해줘서
type='submit'
>
// 10. state가 변경(isEditing)될 땐 -> '수정',
// 아닐 땐 '제출'하는 버튼으로 변경
{isEditing ? '수정' : '제출'}
</button>
</form>
)
}
}
// ...

const handleSubmit = e => {
e.preventDefault();
if (charge !== "" && amount > 0) {
// 11.
if (isEditing) {
// 수정
// 객체 배열들을 순회
const newExpenses = expenses.map(item => {
// 현재 객체들의 id와 state의 id와 같을 땐 어떠한 걸 만들고
// 아닐 땐 item을 리턴함 (item-> 수정 없는 객체들, {} -> 수정한 객체)
return item.id === id ? { ...item, charge, amount } : item
})
setExpenses(newExpenses);
setIsEditing(false);
} else {
// 새로 생성
const newExpenses = {
id: cryto.randomUUID(),
charge,
amount
}
}
// ...
// ...
기존에 있는 데이터 얕은 복사하기➡...item
뒤에 있는 애들로 덮어씌우기➡charge, amount
기존 데이터에서 expenses.map 부분 로직을 통해서
수정 끝났으니 false로 변경

⬆ App.js에 있는 배열을 비워주면 되겠죠❓// ...
<button>
목록 지우기
</button>
// ...
이 버튼 눌렀을 때 발생
⬇
const App = () => {
// ...
// 1. 함수 만들기
// 배열 안에 있는 것들을 비워줘야 되니까
// setExpenses 이용해서 ([]) 비워주기만 하면 됨
// 2. clearItems 얘를 호출해야해서 <ExpenseList 밑으로 가기
const clearItems = () => {
setExpenses([]);
}
// ...
return (
<main>
<h1>예산 계산기</h1>
// ...
<ExpenseList
initialExpenses={expenses}
handleDelete={handleDelete}
handleEdit={handleEdit}
// 2. 넣어주기
clearItems={clearItems}
/>
// ...
)
}
export default App
⬇
// ...
const ExpenseList = ({
initialExpenses,
handleDelete,
handleEdit,
// 3. 구조 분해 할당에 clearItems 넣어주기
clearItems
}) => {
return (
<>
<ul>
{
// ...
}
</ul>
{/* 4. 클릭했을 때니까 onClick={}
onClick={() => clearItems()} === onClick={clearItems} */}
<button onClick={clearItems}>
목록 지우기
</button>
</>
)
}
export default ExpenseList

// 1. 파일 만들고 코드 넣기
import React from 'react';
const Alert = ({ text, type }) => {
return (
<div className={`alert alert-${type}`}>
{text}
</div>
)
}
export default Alert;
⬇
const App = () => {
// ...
// 3. 알림창 어떨 땐 보이고 어떨 땐 안보여야 해서
// state 생성해주기
const [alert, setAlert] = useState({ show: false });
// 4. 컨드롤 해줄 함수 만들기
// 삭제 버튼 누르거나 어떤 걸 생성할 때➡true로 되게
const handleAlert = ({ type, text }) => {
let a;
clearTimeout(a);
setAlert({ show: true, type: type, text: text });
// 7초 뒤에 alert창 안보이게 하기
a = setTimeout(() => {
setAlert({ show: false });
}, 7000);
}
// ...
return (
<main>
{/* 2. Alert.js를 <Alert /> 컴포넌트로 넣어주기
예산 계산기 위에 alert 창이 뜨게 해야 하니까,
알람창은 항상 보이면 안돼서
이것을 컨트롤 하기 위한 state 생성하기
'alert.show'가 true이면 <Alert /> 컴포넌트를 렌더링,
그렇지 않으면(null) 렌더링하지 않음 */}>
{alert.show ? <Alert text={alert.text} type={alert.type} /> : null}
<h1>예산 계산기</h1>
// ...
⬆ // 5. 제출 버튼 눌렀을 때 코드 부분
➕코드 추가 하기
이렇게 지출 항목 & 비용 아무것도 작성 안하고 제출 버튼 누르면 에러 alert창 뜸
➕코드 추가 하기
🗑삭제 버튼 누르면 alert창 메시지 뜸

컨트롤 C + V
🔄깃허브 새로고침하면 올라가있음
깃허브 유저 이름 복사하고
https:// 뒤에 붙여넣기
저장소 이름 복사하고
붙여넣기
코드 추가해주기