
지금까지 만들어진 페이지의 item 목록은 App 컴포넌트에서 정의한 data들로, ExpenseForm으로 입력 받은 값들이 적용되지 않는 정적인 상태로 존재한다.
이를 ExpenseForm에서 동적으로 렌더링 할 수 있게 만들어보고자 한다.
먼저 ExpenseFilter 컴포넌트를 통해 선택된 year와 같은 연도를 가진 item만 화면에 출력해보자.
// ExpenseFilter
export default function ExpensesFilter(props) {
const dropdownChangeHandler = (event) => {
const selectedYear = event.target.value;
props.onChangeFilter(selectedYear);
};
return (
<div className="expenses-filter">
<div className="expenses-filter__control">
<label>Filter by year</label>
<select value={props.selected} onChange={dropdownChangeHandler}>
<option value="2022">2022</option>
<option value="2021">2021</option>
<option value="2020">2020</option>
<option value="2019">2019</option>
</select>
</div>
</div>
);
}
------------------------------------------------------------------------
// Expenses
export default function Expenses(props) {
const [filteredYear, setFilteredYear] = useState("2021");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
// map() 사용
{props.items.map((expenses) => (
<ExpenseItem
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
기존 Expenses 코드는 ExpenseItem을 item마다 하나씩 작성해줘야했는데, map 메서드를 사용해 훨씬 더 간결하게 만들어줬다.
이제 사용자가 선택한 item만 출력될 수 있게 filter 메서드를 사용해보았다.
export default function Expenses(props) {
const [filteredYear, setFilteredYear] = useState("2021");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
// props로 부터 받아온(사용자가 선택) Year와 item의 Year가 같은 걸 골라냄
const filteredExpenses = props.items.filter(
(expense) => expense.date.getFullYear().toString() === filteredYear
);
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
{filteredExpenses.map((expenses) => (
<ExpenseItem
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
</Card>
</div>
);
}
그리고 만약 사용자가 입력한 연도에 item이 없다면, 이를 화면에 표시하여 사용자에게 알려주는 기능을 구현해보자.
그런데 JSX 내에서는 if문을 사용할 수 없다. 때문에 삼항 연산자를 사용하여 표현해보고자 한다.
// Expenses.js
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
/* filteredExpenses가 없다면 사용자가 알 수 있도록 화면에 표시해주기 */
{filteredExpenses.length === 0 ? (
<p>no expenses found!</p>
) : (
filteredExpenses.map((expenses) => (
<ExpenseItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))
)}
</Card>
</div>
);
}
삼항 연산자는 && 연산자를 통해 더 간결하게 표현이 가능하다.
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
/* filteredExpenses가 없다면 사용자가 알 수 있도록 화면에 표시해주기 */
{filteredExpenses.length === 0 && <p>no expenses found!</p>}
{filteredExpenses.length > 0 &&
filteredExpenses.map((expenses) => (
<ExpenseItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
))}
</Card>
</div>
);
}
마지막으로 JSX 외부로 빼서 if문을 작성하여 코드를 만드는 것도 가능하다.
// 초기 상태를 item이 존재하지 않았을 경우로 설정
let expensesContent = <p>no expenses found!</p>;
// 만약 해당 연도에 item이 있다면 그 item들을 출력
if (filteredExpenses.length > 0) {
expensesContent = filteredExpenses.map((expenses) => (
<ExpenseItem
key={expenses.id}
title={expenses.title}
amount={expenses.amount}
date={expenses.date}
/>
));
}
return (
<div>
<Card className="expenses">
.
.
.
onChangeFilter={filterChangeHandler}
/>
/* filteredExpenses가 없다면 사용자가 알 수 있도록 화면에 표시해주기 */
{expensesContent}
</Card>
</div>
);
}
이제 선택한 연도별로 달 마다 그래프를 나타내는 차트를 추가해보자.
// Chart.js
import ChartBar from "./ChartBar";
export default function Chart(props) {
return (
<div className="chart">
{props.dataPoints.map((dataPoint) => (
<ChartBar
key={dataPoint.label}
value={dataPoint.value}
maxValue={null}
label={dataPoint.label}
/>
))}
</div>
);
}
먼저 Chart를 위한 컴포넌트를 모아두는 Chart 폴더를 만든 뒤, Chart 컴포넌트를 만들었다.
Chart 컴포넌트는 props를 통해 ChartBar 컴포넌트로 부터 key, value, maxValue, label을 받아와 mapping 해주었다.
key는 mapping을 하기 때문에 필요한데 label이 고유한 값이기 때문에 그대로 사용해주었다.
// ChartBar.js
export default function ChartBar(props) {
let barFillHeight = "0%";
// barFillHeight가 0 초과라면 value를 퍼센트로 나타냄
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이 동적으로 변해야하기 때문에 자바스크립트 객체를 사용
style={{ height: barFillHeight }}
></div>
</div>
<div className="chart-bar__label">{props.label} </div>
</div>
);
}
ChartBar 컴포넌트는 각 달 마다의 amount 양을 나타내는 하나의 막대 그래프라고 생각하면 되는데, 그래프의 값이 동적으로 변해야하기 때문에 barFillHeight을 선언한 뒤 초기 값을 0%로 설정해주었다. 이후 퍼센트로 변환한 값을 넣어주었다.
그리고 위에서 퍼센트로 변환한 값을 화면에 출력하기 위해서는 style을 바꿔줘야 하는데 style을 바꾸는 방법은 크게 두가지가 있다.
지금까지 작성한 코드에서는 css 관련 코드를 지워서 보이지 않았지만 사실 css 파일을 import 해오는 방법을 사용하고 있었다.
그런데 지금까지 해오던 .css 파일로는 동적인 스타일을 적용하기 어렵기 때문에 inline으로 style을 추가해줬다.
let barFillHeight = "0%";
// barFillHeight가 0 초과라면 value를 퍼센트로 나타냄
if (props.maxValue > 0) {
barFillHeight = Math.round((props.value / props.maxValue) * 100) + "%";
}
.
.
.
<div
className="chart-bar__fill"
// style이 동적으로 변해야하기 때문에 자바스크립트 객체를 사용
style={{ height: barFillHeight }}
></div>
해당 코드만 가져와 다시 설명을 해보자면, 위쪽에는 값을 퍼센트로 변환한 barFillHeight가 있고
이를 바로 height의 값으로 전달해주었다.
이때, style={ }안에 이중 괄호를 사용했는데 이렇게 사용하면 변수를 사용하지 않아도 값을 바로 넘겨줄 수 있다.
// 1. 변수 선언
let chartHeight = { height: 80% }
<div style={ chartHeight }></div>
// 2. 이중 괄호
<div style={{ height: 80% }}></div>