[React] CRUD 예산 계산기 💰👩‍💻

프린이·2024년 11월 8일

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


✋하나의 페이지에서 어떠한 컴포넌트들로 구성할지🌟


👩‍💻

⬇ 클래스 쓰다가 함수형 컴포넌트로 넘어갈 것App을 export 해줘야 함
👉 index.js 에서 App 컴포넌트를 가져오고 있기 때문❗

🧐 클래스 컴포넌트

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

🎨스타일

👉 원하는 방식대로

📁src 안에 각각의 compontent 만들어주기

✨ES7 설치 후 편하게 코드 작성하기

rcc(리액트 클래스 컴포넌트) 축약으로 코드 간편하게 생성하기 😎
👉 함수형 컴포넌트 : rafce

🧩다른 컴포넌트➡다른 컴포넌트에 가져올 때

👉 default 클래스 써서 import할 때 이름 아무렇게 지어도 되지만( 이해 쉽게 통일⭕ )

👩‍🦱부모➡👩자녀 컴포넌트 사용할 때🤙

🌟 꺽쇠로 가져오고 대문자로 시작할 것 ❗❗

😮React.Fragment🤙

리액트에선 부모 태그를 감싸줘야하는 룰이 있는데
div 불필요한 태그 쓰기 싫을 때
➡ React.Fragment 사용하면 해결🙆‍♀️👍⬆ 생략 시 위처럼 사용할 것😎

🤭icon 쓰고 싶을 땐 icon 라이브러리 사용하기1 ) 설치하기npm install react-icons --save

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

💰가짜 지출 데이터 생성하기

👉 새로고침 했을 때 보여지는 더미 데이터들🤗

🔥props(Properties) 사용 : 👩‍🦱App➡👩ExpenseList➡👩ExpenseItem

👩‍🦱App➡👩ExpenseList로 데이터 전달한 것❗
자녀➡부모는 props❌ 자녀 : Read Only( 데이터 수정❌ )
👩ExpenseList➡👩ExpenseItem👉 에러 : 유니크한 key prop 있어야 함🔑이젠 에러 Byeee👋

잠깐✋ 'JSX Key 속성' 이해하기

리액트에서 요소의 리스트를 나열할 땐 Key 넣어줘야 함🔑
키는 리액트가 변경, 추가 또는 제거된 항목을 식별하는 데 도움 됨
요소에 안정적인 ID 부여하려면 배열 내부의 요소에 키를 제공해야함🙆‍♀️

이전 돔 & 현재 돔 두 개의 돔을 비교(DIFF) 해서 다른 부분만 실제 돔에 적용✨
비교 하는 과정에서 리액트가 스스로 하는 부분이 있고
우리가 도움을 줘야 되는 부분이 있음✅도움을 줄 수 있는 부분 중 하나가 key 프랍 넣어주는 것🔥만약 리스트 맨 뒤에 추가 했다고 하면 리액트는 이정돈 알 수 있음
👉 리액트 : "새로운 아이가 추가 되었구나"
하지만 첫 번째에 리스트를 추가하면 리액트는 모름😕❓
👉 리액트 : "첫 번째가 바꼈으니 뒤에도 다 바꼈구나" ( 판단 불가😬 )
유니크한 값을 준 리스트 추가되면 "아하 그 애만 추가 되었구나" ( 판단 가능😎 )


🗑삭제하기 - src > component > ExpenseItem.js

<button
  onClick={() => console.log(`${this.props.expense.id} clicked`)}>
  <MdDelete />
</button>
  

클릭하면 콘솔에 잘 나옴

🎈객체 지우기 - 👩‍🦱부모인 App.js에서

App.js

// 어떠한 걸 지웠는지 알아야 하기에 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.js

// ExpenseList-> ExpenseItem으로 다시 한번 더 데이터 내려주기 props
<ExpenseItem
  	handleDelete={this.props.handleDelete}
/>

ExpenseItem.js

<button 
  onClick={() => 
  	this.props.handleDelete(this.props.expense.id)
  }>
  <MdDelete />
</button>

아하 함수도 props로 내릴 수 있겠구나 ❗❗렌트비를 삭제 버튼 누르면 콘솔에 그 객체 부분만 없어짐 ( 화면에선 변화 없음😕 )

리액트에선 한 가지 규칙이 더 있음
바뀐 데이터를 화면에 다시 보여주게 하려면❓
👉 'State' 사용해야 함🔥
얘가 변경되면 가상돔 하나 더 만들어서
그 전 가상돔과 이후의 가상돔을 비교해서 진행되는 것❗

😎데이터를 State로 변경해주기

👉 클래스에서 state 만들기 ( 🧠컴포넌트 안에서 기억하고 싶을 때 사용🙆‍♀️ )

컴포넌트 안에서 무언가를 기억하고 싶을 때
➡'변수' 만들어도 되고 'ref' 이용해서 기억할 수도 있고
'state' 만들거나 3가지 방법으로 가능 ❗❗

🎆state 생성하기

constructor (props) {
  super(props);
  this.state = {
    expenses: [
      { id: 1, charge: '렌트비', amount: 1600 },
      { id: 2, charge: '교통비', amount: 400 },
      { id: 3, charge: '식비', amount: 1200 },
    ]
  }
}

👀이렇게 변경해주기➡state 만듦✨

🎈state 사용하기

Props 사용했을 때랑 같음 ( this.props.name )
👉 this.state.expenses ( 이름으로 넣어주기 )

App.js

// 어떠한 걸 지웠는지 알아야 하기에 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                        
  )

🔄this.initalExpenses➡this.state.expenses로 변경하기

<ExpenseList
  	initialExpenses={this.initialExpenses}
  	handleDelete={this.handleDelete}
/>

⬇

<ExpenseList
  	initialExpenses={this.state.expenses}
  	handleDelete={this.handleDelete}
/>

🔄{this.initialExpenses}➡{this.state.expenses}로 변경하기

😎에러 싹 없어짐🔥

🆙state 업데이트 하기⤴

원래 있던 이 데이터에서 하나의 데이터를 삭제 했을 때는 ⬇없앤 거를 state에 expenses의 값으로 업데이트 해줘야 함여기서 state 값을 변경해 주면 됨🙆‍♀️✅
👉 setState 함수 사용 ❗❗
{ 객체 안에 변경할 state 넣어주기➡expenses }
어떠한 값을 업데이트 할거냐면➡newExpenses ( "새로운 값으로 업데이트 할 거야" )

App.js

// 어떠한 걸 지웠는지 알아야 하기에 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가 변경이 되고 state가 변경이 되면
가상돔 만든 다음에 이전과 비교(DIFF)를 해서 변경이 된 것을 반영해준 것😎


🔄전체를 클래스➡함수 형으로 바꾸자❗

ExpenseForm.js

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>
      )
    }
}      

⬇

rafc

import 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>
    )
}
 

클래스에서 함수형으로 바꿔줌 쨔잔😮

ExpenseItem.js

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

매개변수에 props 넣어주고 this. 다 빼주기🙆‍♀️

App.js

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는 어떻게 바꾸는지❓
👉 함수형에선 리액트에서 제공해주는 hook들이 있는데 이걸 사용❗
const [state, setState] = useState(초기값)statestate 초기값state 업데이트

ExpenseList.js

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

👍


➕리스트 추가하는 기능 만들기

지출 항목 & 비용 입력하고 제출 버튼 누르면
밑에 리스트 항목에 추가 되게 기능 넣기🤟

리액트에선 state 값만 변경해주면 자동으로 알아서
화면에서 다시 렌더링 해줌 ( 업데이트 해서 화면에 보여질 수 있게 해줌 )😮✨

우선 이 타이밍하고 있는 값들을 App 컴포넌트에서 갖고 있어야 함카드비 라는 이 값을 App에서 기억을 하고 있어야 한다는 말🧠

🔥이 값을 기억할 수 있게 해주는 state를 만들자❗

App.js

const [charge, setCharge] = useState('');
// 비용 input 부분
const [amount, setAmount] = useState(0);

🔗input 값과 state 연동하기

// 값 내려주기 props
<ExpenseForm charge={charge} />

⬇

😎ExpenseForm에 와서 props로 받아주기

// ...
const ExpenseForm = ({charge}) => {
  	return (
     	  // ...
          <input
            type='text'
            id='change'
            name='change'
            placeholder='예 ) 렌트비'
            value={charge}
            />
        </div>
        // ...

({charge})➡props로 받아서 구조 분해 할당해주고
value={charge}➡input 안에 지정해줌빈 문자열로 초기 값을 줘서 input에 값을 입력해도 아무런 변화❌😕
👉 타이핑할 때 state 값 계속 변경해주면 됨🙆‍♀️ ( setCharge )

App.js

// ...

// 함수 만들기
const handleCharge = () => {
}

// ...

return (
  <main>
    // ...
      <ExpenseForm 
        charge={charge} 
        // 함수 내려주기
        handleCharge={handleCharge}
      />
  	  // ...

⬇

ExpenseForm.js

import React from 'react'

// handleCharge 받아주기
const ExpenseForm = ({ charge, handleCharge }) => {
  	return (
      <form>
        // ...
          <input
            type='text'
            id='change'
            name='change'
            placeholder='예 ) 렌트비'
            value={charge}
            // 이렇게 넣어주면 됨
            onChange={handleCharge}
          />
        // ...

⬇

App.js

// ...

// 함수 만들기
const handleCharge = (e) => {
  setCharge(e.target.value);
}

// ...

이젠 지출 항목 input 타이핑 잘 됨👍

App.js

// ...

// 비용을 위해 함수 만들기
const handleAmount = (e) => {
  
}

// ...

return (
  <main>
    // ...
      <ExpenseForm 
        charge={charge} 
        handleCharge={handleCharge}
        // 넣어주기
        amount={amount}
        handleAmount={handleAmount}
      />
  	  // ...

⬇

ExpenseForm.js

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}
          />
        // ...

⬇

App.js

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 타이핑 되기는 한데
🤔생각을 좀 해봐야함🧠💡
const [amount, setAmount] = useState(0);
👉 amount 값의 타입은❓ : number❗원래 타입은 number인데 string으로 변경한 것😬

🤷‍♀️어떻게 해줘야 할까❓

방법 1 ) Number

const handleAmount = (e) => {
  	console.log(typeof e.target.value);
  	setAmount(Number(e.target.value));
}

방법 2 ) valueAsNumber

// number
const handleAmount = (e) => {
  	// console.log(typeof e.target.valueAsNumber);
  	setAmount(e.target.valueAsNumber);
}

state가 각각의 값들을 기억하고 있음🧠

👆제출 버튼 눌렀을 때 새로운 객체 넣어서 업데이트하기🆙

ExpenseForm.js

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>
      )
    }
}   

App.js

// 폼 제출 시 호출되는 함수
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}
      />
  	  //...

유니크한 id 사용 콘솔에 crypto.randomUUID 입력하면 유니크한 값이 랜덤으로 나옴지출 항목&비용 작성하고 제출 버튼 누르면 리스트 추가 됨 ( 쨔잔😮 )


🧮리스트 총 지출 계산하기

amount 각각 다 더해줘서 총지출 나오게 하면 됨

🔥값 누적해줘야 하니까➡reduce 쓰기

App.js

// ...

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


🖍CRUD 중 가장 어렵다 느끼는 부분 : 수정하기

수정 버튼 누르면 렌트비 2000 이 값들을 위로 올려주는 방식값을 변경 후 수정 버튼 누르면 값이 수정됨

🐱‍🏍구현해보자❗

App.js

// ...

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

⬇

ExpenseList.js

// ...

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}
              />
            // ...

⬇

ExpenseItem.js

// ...

// 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>
        // ...

⬇

App.js

// ...

const App = () => {
  	const [isEditing, setIsEditing] = useState(false);
  
  	// ...
  
    const handleEdit = (id) => {
      // 7. 수정 버튼 누르면 수정된 후니까 true 변경하기
      setIsEditing(true);  
    }
    
    // ...
    
    return (
      <main>
        // ...
      </main>
    )
}
export default App

🖍클릭 했을 때 handleEdit 함수가 호출📢🙆‍♀️

🤷‍♀️여기서 어떻게 해야 할까❓❓

const [isEditing, setIsEditing] = useState(false);
👉 우선 isEditing 바꿔줘야 함🖍 수정 버튼 클릭한 후면 수정된 이후니까 true로 바꿔줘야 함❗❗

🎆state 하나 더 생성하자

👉 수정할 때 어떠한 객체를 수정하고 있는지 알기 위해
( 객체의 id를 넣어줄 state✅ )

App.js

// ...

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

🖍수정 버튼 누르면 해당 데이터들이 어떻게 위로 올라갈까❓

👩‍💻 App.js에서 handleEdit 함수에 코드넣기

App.js

// ...

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);
      // ...
    }
    
    // ...


🎨버튼 UI 수정➡제출로 변경되는 부분

ExpenseForm.js

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

🚨수정 시 기존 게 아닌 새로운 리스트 생성🤯

App.js

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 부분 로직을 통해서
수정된 데이터들이 newExpenses에 들어간 것❗수정 끝났으니 false로 변경


💥목록 지우기 버튼 누르면 전체 리스트 지우기

⬆ App.js에 있는 배열을 비워주면 되겠죠❓

ExpenseList.js

// ...
        <button>
          목록 지우기
        </button>
   // ...

이 버튼 눌렀을 때 발생
⬇

App.js

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

⬇

ExpenseList.js

// ...

const ExpenseList = ({ 
  initialExpenses, 
  handleDelete,
  handleEdit,
  // 3. 구조 분해 할당에 clearItems 넣어주기
  clearItems
}) => {
  return (
    <>
      <ul>
        {
          // ...
        }
        </ul>
        {/* 4. 클릭했을 때니까 onClick={} 
           onClick={() => clearItems()} === onClick={clearItems} */}
        <button onClick={clearItems}>
          목록 지우기
        </button>
    </>
  )
} 
export default ExpenseList

📢아이템이 수정/삭제되었습니다. alert창 만들기💬

👉 div 안에 텍스트(아이템이 수정되었습니다.)가 있는 것
👉 수정 & 삭제는 텍스트, 박스 색상이 다름
👉 다이나믹 하게 만들 것

📁src>component>Alert.js

// 1. 파일 만들고 코드 넣기
import React from 'react';

const Alert = ({ text, type }) => {
  	return (
      	<div className={`alert alert-${type}`}>
          {text}
        </div>
    )
}
export default Alert;

⬇

App.js

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창 메시지 뜸


😼Github 배포하기

🔗원격 저장소랑 로컬이랑 연결해주기

컨트롤 C + V🔄깃허브 새로고침하면 올라가있음

📥깃허브 페이지쓰 설치하기

📁package.json에다 URL 작성하기

깃허브 유저 이름 복사하고https:// 뒤에 붙여넣기저장소 이름 복사하고붙여넣기코드 추가해주기🖍이부분 프라이빗이 아닌 퍼블릭으로 했는데도 실패 하셨다고 함😥

profile
안녕하세요! 퍼블리싱 & 프론트엔드 개발 공부 블로그 입니다!

0개의 댓글