Expense.js파일을 보면 아직 까지는ExpenseItem컴포넌트가 하드코딩 되어 있다.

이렇게 코드를 짜면 Add Expense기능 뿐 아니라 Filter의 기능까지 수행하지 못하는 정적인 코드가 되어버린다.
따라서 expenses 배열에 있는 데이터들을 동적으로 렌더링 해야한다.

이 자바스크립트 함수를 이용해서 동적으로 렌더링한다.
{props.data.map((expenses) => (
<ExpendItem
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
props.data에서 받아온 배열을 이용해 각각의 요소를 expenses로 받는다면 ExpendItem 컴포넌트 안에 집어 넣어서 반환하는 것이다.
이렇게하면 하드코딩 된 ExpendItem 컴포넌트를 제거해도 같은 화면이 보인다.
여태까지는
Add Expense버튼을 눌러도 리스트가 추가되지는 않았다. 이제부터 추가하는 기능을 만들어 보려고 한다.
expenses 데이터 배열을 전역변수인 DUMMY_EXPENSES로 바꿔준다.useState 를 사용하여 추가되는 요소를 expenses에 넣어준다. const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const DataFromNewExpenseToApp = (expense) => {
setExpenses((prevExpense) => {
return [expense, ...prevExpense];
});
};
State 글에서 이전 state에 의존하는 state update 글에도 있지만 위의 코드를 다음과 같이 바꿨을 때는 문제가 생길 수도 있다.
setExpenses([expense, ...expenses]);
왜냐하면 여러 state를 업데이트 할 때에는 겹치는게 있을 수도 있는데 함수를 이용하면 그 업데이트 되는 정보들을 순차적으로 처리할 수 있기 때문이다.
여태까지 만든 앱을 살펴보면
Add Expense버튼을 누를때마다 렌더링을 다시하기 때문에
div1
div2
가 있는데div3을 추가할 상황이 된다면
1 -> 3으로 바꾸고
2- > 1으로 바꾸고
마지막 요소를 추가해 2를 넣을 것이다.
이런 상황을 성능적으로 옳지 못하다. key를 이용하면 react는 개별의 컴포넌트를 이해하고 다시 렌더링 되어 업데이트 되는 일이 없이 추가되는 요소에 대해서만 추가할 것이다.
동적으로 렌더링 하는 부분을 다음과 같이 바꿔준다.
{props.data.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
연도를 선택하면 그 연도에 맞는 데이터만 렌더링 하려고 한다.

{props.data.map((ele) => {
let year = ele.date.getFullYear();
if (year == newDate) {
return (
<ExpendItem
key={ele.id}
title={ele.title}
amount={ele.amount}
date={ele.date}
/>
);
}
})}
const selectedYearFilter = props.data.filter(
(ele) => ele.date.getFullYear().toString() === newDate
);
{selectedYearFilter.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
연도 필터를 이용했을때 컨텐츠가 없다면 없다는 문구를 출력하고 싶을때 쓰는 방법이다.
{selectedYearFilter.length === 0 ? (
<p style={{ color: "white" }}>No Contents Found</p>
) : (
selectedYearFilter.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))
)}
{selectedYearFilter.length === 0 && (
<p style={{ color: "white" }}>No Contents Found</p>
)}
{selectedYearFilter.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
let expenseContents = <p style={{ color: "white" }}>No Contents Found</p>;
if (selectedYearFilter.length > 0) {
expenseContents = selectedYearFilter.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
));
}
ExpenseList.js를 생성하여 컴포넌트를 분리하려고 한다.
컴포넌트를 분리했을 때 장점은 return을 통해서 조건문 반환을 할 수 있다는 점이다. (컴포넌트를 분리하기 전에는 불가능했던 것)
그리고
ExpenseList요소를<ul>로 감싸고ExpendItem컴포넌트들을<li>로 감싼다.
const ExpenseList = (props) => {
let expenseContents = (
<h2 className="expenses-list__fallback">No Contents Found</h2>
);
if (props.item.length === 0) {
return expenseContents;
}
return (
<ul className="expenses-list">
{props.item.map((expenses) => (
<ExpendItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
</ul>
);
};
Add Expense창을 열었다 닫았다 하는 기능을 추가해보려고 한다.
처음 구현을 했을 때에는 addExpenseTab이라는 state 변수를 만들어서 변수로서 컨트롤 하려고 했는데, 더 좋은 방법이 있는 것 같다.
true일때와false일때로 나눠서 구현하는 것이다.


import React from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";
import { useState } from "react/cjs/react.development";
const NewExpense = (props) => {
//
const dataFromFormToNewExpense = (prevData) => {
const newExpenseData = {
...prevData,
id: Math.random().toString(),
};
props.onClickAddExpense(newExpenseData);
setAddExpenseTab(<button onClick={openAddExpense}>open</button>);
};
const afterClickCancel = () => {
setAddExpenseTab(<button onClick={openAddExpense}>open</button>);
};
const openAddExpense = () => {
setAddExpenseTab(
<ExpenseForm
onClickAddExpense={dataFromFormToNewExpense}
onClickCancel={afterClickCancel}
/>
);
console.log("update");
};
let [addExpenseTab, setAddExpenseTab] = useState(
<button onClick={openAddExpense}>open</button>
);
return <div className="new-expense">{addExpenseTab}</div>;
};
export default NewExpense;
const [isEditing, setIsEditing] = useState(false); 을 통해서 bool state를 만듭니다.{!isEditing && <button onClick={openAddExpense}>open</button>}
{isEditing && (
<ExpenseForm
onClickAddExpense={dataFromFormToNewExpense}
onClickCancel={afterClickCancel}
/>
)}
setIsEditing을 바꿔준다.
다음의 차트를 만드는 방법은 꽤나 복잡했어서 강의내용을 전부 이해한 후, 재구성해서 만들었다.
Chart.js 와 ChartBar.js 파일을 만든다.Expenses.js 파일에서 <Chart /> 컴포넌트를 렌더링 할 예정이다. props로는 filteredYear을 넣어준다. 그렇게 되면 해당 연도의 데이터가 전부 넘어가게 된다.<ChartBar /> 컴포넌트의 props로 넘겨준다.ChartBar.js에서 동적 스타일링을 해준다 (Bar 높이)//Chart.js
import React from "react";
import "./Chart.css";
import ChartBar from "./ChartBar";
const Chart = (props) => {
let yearlyData = [
{ label: "Jan", value: 0 },
{ label: "Feb", value: 0 },
{ label: "Mar", value: 0 },
{ label: "Apr", value: 0 },
{ label: "May", value: 0 },
{ label: "Jun", value: 0 },
{ label: "Jul", value: 0 },
{ label: "Aug", value: 0 },
{ label: "Sep", value: 0 },
{ label: "Oct", value: 0 },
{ label: "Nov", value: 0 },
{ label: "Dec", value: 0 },
];
for (let expense of props.data) {
let monthIdx = expense.date.getMonth();
yearlyData[monthIdx].value += expense.amount;
}
let valueArr = yearlyData.map((ele) => ele.value);
let maxValue = Math.max(...valueArr);
return (
<div className="chart">
{yearlyData.map((ele) => (
<ChartBar
key={ele.label}
value={ele.value}
maxValue={maxValue}
label={ele.label}
/>
))}
</div>
);
};
export default Chart;
//ChartBar.js
import React from "react";
import "./ChartBar.css";
const ChartBar = (props) => {
let barFillHeight = "0%";
if (props.maxValue > 0) {
barFillHeight = Math.round((props.value / props.maxValue) * 100) + "%";
}
return (
<div className="chart-bar">
<div className="chart-bar__inner">
<div
className="chart-bar__fill"
style={{ height: barFillHeight }}
></div>
</div>
<div className="chart_bar__label">{props.label}</div>
</div>
);
};
export default ChartBar;