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;