Rendering Lists & Conditional Content
: working with really dynamic content
- Outputting Dynamic Lists Of Content
- Rendering Content Under Certai Conditions
페이지에 데이터 배열을 출력하는 방법, 다른 조건(상황)에서 다른 컨텐츠를 보여주는 방법 등을 알아보자.
1. 데이터의 렌더링 목록
지금까지 expense 데이터 배열은 app.js에 하드 코딩되어 있었다. 또한 Expenses.js를 살펴보면 JSX코드로 ExpenseItem 컴포넌트를 정적으로 하드코딩되어 있다. 몇 개의 아이템이 추가로 입력되고 필터링될 지 알 수 없으므로 이 부분을 동적으로 변환해주어야 한다.
그럼 이 Expenses.js에 있는 ExpenseItem 컴포넌트가 동적으로 렌더링되도록 바꿔보자.
{props.items.map((expense) => (
<ExpenseItem
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
이전에 여러 번 작성했던 ExpenseItem 컴포넌트들은 모두 지우고 그 자리에 위 코드만 작성해주면 된다. 자바스크립트의 map 함수를 이용한다. props.item으로 expenses 데이터 배열을 가져와서 map함수를 이용하여 해당 배열을 JSX코드로 ExpenseItem 컴포넌트의 배열로 바꿔주자. 또 데이터 내용들(title, amount, date)도 지정해주면, 실행 결과가 이전과 같다. 이제 우리는 데이터 배열에 기반하여 컴포넌트를 동적으로 브라우저에 나타내고 있다!
(전체 코드는 아래와 같다)
import React, { useState } from "react";
import ExpenseItem from "./ExpenseItem";
import "./Expenses.css";
import Card from "../UI/Card";
import ExpensesFilter from "./ExpensesFilter";
const Expenses = (props) => {
const [filteredYear, setFilteredYear] = useState("2020");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
{props.items.map((expense) => (
<ExpenseItem
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</Card>
</div>
);
};
export default Expenses;
2. State 저장 목록 사용
이번엔 데이터 배열을 동적으로 변화시켜보자. 현재는 데이터 배열이 const로 선언되어 있는데, 아무리 여기에 입력받은 expense 데이터를 추가해봤자 렌더링되지 않을 것이다. 왜? const이기 때문에! 그러면 어떻게 해야한다? State을 써야한다!
이 데이터 배열을 useState 훅으로 state을 만들어주자.
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
초깃값으로 넣어준 DUMMY_EXPENSES는 기존 데이터 배열을 app 밖으로 꺼내서 전역변수로 놓고 넣어준 것이다. 그러면 이제 expenses 데이터 배열은 state이 되었다.
그렇다면 이번엔 새로 입력된 expense데이터를 불러와 setExpenses로 expenses에 해당 데이터를 추가해주어야 한다.
const addExpenseHandler = (expense) => {
setExpenses([expense, ...expenses]);
};
이렇게 하면 새로운 expense를 데이터 배열에 넣을 수 있겠지...?
라고 생각하겠지만, 이걸 보고 무언가 생각나지 않는다면... 당신 제가 전에 올린 state 관련 포스트를 다시 읽어보세요!
기존 state 배열에 새로운 expense를 추가한다는 것은 새로운 state이 이전의 state에 의존하여 바뀐다는 거랑 같다는 것을 알아야 한다.
그러니까 그냥 위와 같이 setExpense([expense, ...expenses])로 변경하면 리액트가 가장 최근의 state 스냅샷을 가져온다는 보장이 없고, 그렇게 되면 잘못된 내용이 화면에 보여질 수 있다. 따라서 아래와 같이 코드를 작성하여야 한다.
const addExpenseHandler = (expense) => {
setExpenses((prevExpenses) => {
return [expense, ...prevExpenses];
});
};
setExpenses 안에 함수를 이용하면 리액트가 확실히! 가장 최근의 state 스냅샷을 가져와 state을 업데이트해준다.
(전체 코드는 아래와 같다)
import React, { useState } from "react";
import Expenses from "./components/Expenses/Expenses";
import NewExpense from "./components/NewExpense/NewExpense";
const DUMMY_EXPENSES = [
{
id: "e1",
title: "Toilet Paper",
amount: 94.12,
date: new Date(2020, 7, 14),
},
{ id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
{
id: "e3",
title: "Car Insurance",
amount: 294.67,
date: new Date(2021, 2, 28),
},
{
id: "e4",
title: "New Desk (Wooden)",
amount: 450,
date: new Date(2021, 5, 12),
},
];
const App = () => {
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const addExpenseHandler = (expense) => {
setExpenses((prevExpenses) => {
return [expense, ...prevExpenses];
});
};
return (
<div className="App">
<NewExpense onAddExpense={addExpenseHandler} />
<Expenses items={expenses} />
</div>
);
};
export default App;
이젠 입력 제출하면 해당 데이터가 ExpenseItme에 담겨서 보여진다.
그런데...!
3. "Keys" 이해하기
앞의 내용까지 마치면 잘 실행되긴 하는데..
콘솔창을 열어보면 아래와 같은 경고 문구가 떠있다.
https://sentry.io/answers/unique-key-prop/
https://sentry.io/answers/unique-key-prop/
sentry.io
먼저 개발자도구를 열어서 elements창을 열어놓은 상태에서 새로운 expense를 작성해 제출 버튼을 눌러보자. 그럼 마지막 ExpenseItem <div>가 깜빡이는 것을 볼 수 있는데(깜빡인다는 것 그 부분이 브라우저에 추가되거나 변경되었다는 뜻)... 화면을 보면 새로 추가된 ExpenseItem은 가장 첫번째 줄에 나타난다. 왜 일까? 이유는 지금 리액트는 새로 추가된 아이템을 어디에 추가해야 하는지를 모르기 때문이다. 그래서 이런 경우에 리액트는 ExpenseItem들을 하나 하나 그 내용을 확인해서 re-rendering하게 된다. 첫번째 ExpenseItem 안에 다른 h2태그를 자세히 살펴봐보자. 새로운 입력을 제출하면 거기도 깜빡인다. 마지막 아이템도 깜빡이고 첫 아이템인다.
이것은 리액트가 새로운 아이템을 처음에는 마지막 div에 추가하고 각 div들의 내용을 update해서 컨텐츠를 교체한다는 것을 보여준다.
이 상태로 냅두는 것은 성능적으로 좋지않다. 리액트가 모든 아이템들을 확인해서 업데이트하는 과정을 가지는 것으로 버그가 발생할 수도 있다.
이런 문제를 해결하려면 아이템에 "key" props를 추가해주면 된다. "key" props는 우리가 만든 커스텀 컴포넌트에서도 props 관련 실행코드를 따로 작성하지 않아도 리액트가 자동으로 인식해 처리한다. key는 각 아이템별 고유값을 지정하여 리액트가 모든 아이템을 훑고 그걸 모두 업데이트할 필요 없이, 아이템들을 식별해서 추가된 특정 아이템만을 rendering할 수 있게 한다.
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
앞에 말했듯이 key props 관련 코드를 ExpenseItem 컴포넌트에 추가로 작성할 필요 없이, 그냥 key값만 지정해주면 된다. 이 key 값은 고유(unique) 값으로 지정해주어야 하고 우리는 데이터가 각각 고유id를 가지므로 id속성을 key 값으로 지정해주었다.
그리고 역시 개발자도구 elements를 켠채로 아이템을 추가하고 상태 변화를 살펴보면... 이번엔 새로운 아이템이 곧바로 첫번째 부분에 깜빡이며 추가되는 것을 볼 수 있다(경고 문구도 사라짐).
그러니 데이터 배열을 mapping해서 화면에 보일 때는 이 key속성을 꼭 지정하자!
4. Lists 다루기
1-3까지 배운 걸 기반으로 연도 filtering 기능을 구현해보자.
ExpensesFilter 컴포넌트에서 특정 연도를 선택하면 해당 연도의 데이터만을 필터링해서 보여주는 기능이다.
- 자바스크립트의 filter() 함수를 사용하자.
- 기존 expenses 데이터를 바꾸지 않고 필터링해야 한다.
데이터를 필터링할 때 기존 데이터를 변경하면 안된다. 기존 데이터를 이용하여 필터링 기준에 맞는 아이템 부분집합을 구하는 방식으로 해야한다.
ExpensesFilter 컴포넌트를 포함하고 Expenses.js 파일에 아래 코드를 추가하였다.
const filteredExpenses = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
props.items로 받은 expenses 데이터 배열을 필터링하여 새로운 배열을 구성한다.
Date객체랑 연도 비교할 때, getFullYear()하고 toString()으로 문자열로 변환 해주는 것을 명심하자! 처음에 혼자 할 때 계속 안되서 결국 강의로 답을 봤는데, 기본 로직은 위와 같았으나 문자열 변환을 해야되는지 몰라서....
그리고
{filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
JSX코드 부분에서 기존엔 props.items로 매핑했던 것을, 필터링하여 새로 구성한 배열인 filteredExpenses로 바꾸어서 매핑해주면 필터링 기능이 올바르게 작동한다.
(전체 코드는 아래와 같다)
import React, { useState } from "react";
import ExpenseItem from "./ExpenseItem";
import "./Expenses.css";
import Card from "../UI/Card";
import ExpensesFilter from "./ExpensesFilter";
const Expenses = (props) => {
const [filteredYear, setFilteredYear] = useState("2020");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
const filteredExpenses = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
{filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</Card>
</div>
);
};
export default Expenses;
5. conditional content 출력하기
이제 필터링이 잘 작동한다. 그런데 필터링 결과 데이터가 없는 것에 대해서 그냥 빈 칸이 아닌 다른 화면을 보이고 싶다. 이런 것이 바로 conditional content(:different output under different condition) 이다.
{filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
즉, Expenses 컴포넌트 코드에서 filteredExpenses가 공백인 경우엔 다른 화면을 렌더링하겠다는 의미이다.
이때 조건문을 사용해야하는데,
(조건문) ? (true일 경우 실행문) : (false일 경우 실행문)
위와 같은 형태의 삼항 조건 연산자의 형태로 작성한다.
{filteredExpenses.length === 0 ? (
<p>no expenses found.</p>
) : (
filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))
)}
그러면 필터링 결과 해당하는 데이터가 존재하지 않는 경우, 위와 같은 결과가 나온다.
그런데! 위 코드는 조금 보기 불편한 느낌이 없지 않아 있다. 보기 쉬운 코드로 수정해보자. 역시 조건문이지만 && 연산자를 활용했다.
{filteredExpenses.length === 0 && <p>no expenses found.</p>}
{filteredExpenses.length > 0 &&
filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
이렇게 filteredExpenses 배열 길이가 0인 경우, 0보다 큰 경우를 나누어 조건문을 작성하였고, 각각 &&연산자 다음 코드가 실행되어 결국 삼항 조건 연산자와 같은 결과가 나타난다.
(한번 더)그런데! 이보다도 더 깔끔한 방식이 있다. 지금까지는 JSX코드 내에서 조건문을 이용했는데, 이번엔 if함수로 리턴문 밖에서 변수를 이용하는 방법을 살펴보자.
let expensesContent = <p>no expenses found.</p>;
if (filteredExpenses.length > 0) {
expensesContent = filteredExpenses.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
));
}
리턴문 밖에서 위와 같이 JSX코드를 반환하는 expensesContent 변수를 선언해준다. default값은 데이터가 없는 경우에 나타나는 것으로 해주었다. 그리고 if 조건문으로 데이터가 존재할 경우, expensesContent의 값을 override해주어 아이템이 보여지도록 한다.
그리고 이 변수를 JSX코드 리턴문 안에 {expensesContent}로 사용해주면 역시 같은 결과가 나온다. 그러면 JSX코드가 굉장히 깔끔해짐!
위 3가지 방법(JSX코드 리턴문에서 삼항조건연산자 혹은 &&조건연산자 사용, 리턴문 밖에서 변수와 if조건문 사용)을 모두 기억해서 적절히 사용하도록 합시다!
6. 조건 명령문 반환 추가하기
conditional content 부분을 따로 빼내어 컴포넌트로 작성해보려고 한다.
conditional content를 ExpensesList 컴포넌트로 만들었다.
import React from "react";
import ExpenseItem from "./ExpenseItem";
import "./ExpensesList.css";
const ExpensesList = (props) => {
if (props.items.length === 0) {
return <h2 className="expenses-list__fallback">Found no expenses.</h2>;
}
return (
<ul className="expenses-list">
{props.items.map((expense, index) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</ul>
);
};
export default ExpensesList;
Expenses로부터 filteredExpenses를 props.items으로 전달받아 그 데이터가 없는 경우와 있는 경우 리턴 값을 if문을 이용하여 다르게 하였다.
Expenses 컴포넌트에서는 기존 conditional content 관련 코드를 빼고 ExpensesList를 import하여
<ExpensesList items={filteredExpenses} />
이렇게 사용해주면 된다.
그럼 역시 아까와 마찬가지로 해당하는 데이터가 있는 경우엔 데이터 목록이, 데이터가 없는 경우에는 다른 내용이 출력된다.
7. conditional content 한번 더
또 다른 conditional content를 프로젝트에 적용해보려고 한다.
처음 사이트에 접속했을 때는 1과 같이 화면이 나타나고, Add Expense버튼을 클릭하면 화면 구성이 2와 같이 바뀌게 하려고 한다. 또한 2 속 폼에서 expense를 추가하지 않고 Cancel버튼을 누르거나, expense를 작성하고 Add Expense버튼을 눌러 제출하면 다시 1과 같은 화면으로 돌아가게 할 것이다.
방법1: 내가 혼자 고민해서 한 방법으로, ExpenseForm 컴포넌트 내의 코드만 수정하였다.
- 입력폼이 나타나는지 아닌지 여부를 나타내는 boolean 타입의 state 선언
- state이 false이면(조건문) add expense버튼만 보이게 리턴
- add expense와 cancel버튼에 기존 state을 반대로 바꿔주는 기능을 하는 클릭 이벤트 리스너 지정
import React, { useState } from "react";
import "./ExpenseForm.css";
const ExpenseForm = (props) => {
const [visible, setVisible] = useState(false); //화면에 보여질지 여부 state
const clickHandler = () => { //state을 기존의 반대로 바꿔줌
setVisible((prevState) => {
return !prevState;
});
};
const [enteredTitle, setEnteredTitle] = useState("");
const [enteredAmount, setEnteredAmount] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const titleChangeHandler = (event) => {
setEnteredTitle(event.target.value);
};
const amountChangeHandler = (event) => {
setEnteredAmount(event.target.value);
};
const dateChangeHandler = (event) => {
setEnteredDate(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate),
};
props.onSaveExpenseData(expenseData);
setEnteredTitle("");
setEnteredAmount("");
setEnteredDate("");
setVisible((prevState) => {
return !prevState;
});
};
if (visible === false) { //state값 조건 확인 후 리턴(이 코드 위치는 반드시 여기여야만 함!주의)
return <button onClick={clickHandler}>Add Expense</button>;
}
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input
type="text"
value={enteredTitle}
onChange={titleChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Amount</label>
<input
type="number"
min="0.01"
step="0.01"
value={enteredAmount}
onChange={amountChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max="2022-12-31"
value={enteredDate}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button onClick={clickHandler}>Cancel</button>
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseForm;
그래서 위와 같이 코드를 작성하였고 기능 구현에 성공했다.
방법2: 이건 강의에서 작성한 코드로, NewExpense와 ExpenseForm 컴포넌트의 코드를 수정하였다.
- NewExpense에서 입력폼이 화면에 보여질지 여부를 결정짓는 boolean타입의 state 선언
- NewExpense JSX코드에서 조건문을 이용해 state 값에 따라 다른 컴포넌트를 지정
- setState으로 state 값을 바꾸는 기능의 클릭 핸들러 작성 및 지정, 전달(ExpenseForm으로)
import React, { useState } from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";
const NewExpense = (props) => {
const [isEditing, setIsEditing] = useState(false);
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
props.onAddExpense(expenseData);
setIsEditing(false);
};
const startEditingHandler = () => {
setIsEditing(true);
};
const stopEditingHandler = () => {
setIsEditing(false);
};
return (
<div className="new-expense">
{!isEditing && (
<button onClick={startEditingHandler}>Add New Expense</button>
)}
{isEditing && (
<ExpenseForm
onSaveExpenseData={saveExpenseDataHandler}
onCancel={stopEditingHandler}
/>
)}
</div>
);
};
export default NewExpense;
import React, { useState } from "react";
import "./ExpenseForm.css";
const ExpenseForm = (props) => {
const [enteredTitle, setEnteredTitle] = useState("");
const [enteredAmount, setEnteredAmount] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const titleChangeHandler = (event) => {
setEnteredTitle(event.target.value);
};
const amountChangeHandler = (event) => {
setEnteredAmount(event.target.value);
};
const dateChangeHandler = (event) => {
setEnteredDate(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate),
};
props.onSaveExpenseData(expenseData);
setEnteredTitle("");
setEnteredAmount("");
setEnteredDate("");
};
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input
type="text"
value={enteredTitle}
onChange={titleChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Amount</label>
<input
type="number"
min="0.01"
step="0.01"
value={enteredAmount}
onChange={amountChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max="2022-12-31"
value={enteredDate}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="button" onClick={props.onCancel}>
Cancel
</button>
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseForm;
처음에는 나 역시 NewExpense컴포넌트에서 조건문을 이용해서 <button> 혹은 <ExpenseForm>을 보이도록 하려고 했지만, 어떻게 ExpenseForm에서의 클릭이 NewExpense에 선언된 state을 바꿀 수 있는지 방법을 막 찾다가 결국 방법1로 해결하게 되었다. setState을 전달해야 되나 했지만, 해보니까 그것도 안되어서 고민했는데 그냥 setState()의 기능을 하는 함수를 만들어 넘기면 되는 것였다. 기억하자!
그리고 추가로 방법2에 적힌 startEditingHandler랑 stopEditinHandler를 하나로 묶어서
const changeEditingHandler = () =>{
setIsEditing((prevState)=>{
return !(prevState);
}
)
};
이렇게 하나로 써서 해도 될 듯 하다. 실제로 이렇게 하나로 써서 실행해보았는데 잘 되었음!
8. 데이터 차트 추가하기
이번엔 데이터를 시각적으로 보여주는 차트를 만들어 볼 것이다.
먼저 차트는 크게 두 가지 컴포넌트로 구성할 것이다. 하나는 차트 속 특정 바를 구성하는 ChartBar 컴포넌트, 그리고 이 차트바들을 모아 전체적인 차트를 보여주는 Chart 컴포넌트이다.
import React from "react";
import "./Chart.css";
import ChartBar from "./ChartBar";
const Chart = (props) => {
return (
<div className="chart">
{props.dataPoints.map((dataPoint) => (
<ChartBar
key={dataPoint.label}
value={dataPoint.value}
maxValue={null}
label={dataPoint.label}
/>
))}
</div>
);
};
export default Chart;
차트는 여러 개의 차트바를 포함하고 있다. 이 프로젝트의 경우, 항상 12달의 차트를 모두 보여주는 것이 아니라 더 유연하게 flexable하게 구성하기 위해서 props로 받아온 데이터를 매핑하여 동적으로 ChartBar 컴포넌트를 보여주게 하였다. (동적으로 구성할 때 컴포넌트에 key 속성 주는 거 잊지말기!)
9. 동적 스타일 추가하기
이번엔 ChartBar 컴포넌트를 작성해줄 것이다.
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;
여기서는 chart-bar__fill 클래스의 div에 props로 전달받은 데이터 value를 이용해서 해당하는 데이터에 대한 차트바의 높이를 결정짓는다. barFillHeight 변수를 처음에는 0으로 설정해놓고 전달받은 데이터들을 이용하여 그 값을 변경하여 <div>의 css 스타일을 동적으로 변경해준다.
<div
className="chart-bar__fill"
style={{ height: barFillHeight }}
></div>
여기가 동적으로 스타일 적용한 부분이니 확인!
10. 마무리 및 다음 단계
Chart를 ExpensesChart에 담아서 보여줄 것이다.
1. ExpensesChart
ExpensesChart 컴포넌트에서는 필터링된 연도의 expenses 데이터들에 대하여 달별 amount 값을 합산하여 데이터화한다.
import React from "react";
import Chart from "../Chart/Chart";
const ExpensesChart = (props) => {
const chartDataPoints = [
{ 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 (const expense of props.expenses) {
const expenseMonth = expense.date.getMonth(); //starting at 0(Jan->0)
chartDataPoints[expenseMonth].value += expense.amount;
}
return <Chart dataPoints={chartDataPoints} />;
};
export default ExpensesChart;
Expenses에서 ExpensesChart를 사용할 때 props.expenses라는 이름으로 필터링된 데이터 filteredExpenses를 넘겨주고 이를 활용해 위와 같이 달별로 합산해준다.
<ExpensesChart expenses={filteredExpenses} />
Expenses에서 위와 같이 이용하면 된다.
이렇게 필터링, 입력폼, 차트 기능을 data list rendering하고 conditional content를 적용하여 구현하였다.
.
.
.
.
어렵다....^^
'React > (Udemy)React-The Complete Guide' 카테고리의 다른 글
[React] 리액트 앱 디버깅하기(섹션7) (0) | 2022.06.01 |
---|---|
[React] 리액트 컴포넌트 스타일링(세션 6): styled components, css모듈 (0) | 2022.05.28 |
[React] 리액트 state, event 처리(섹션4) (0) | 2022.05.16 |
[React] 컴포넌트 실습(섹션3-2) (0) | 2022.04.15 |
[React] 리액트 기초 컴포넌트(섹션3-1) (0) | 2022.04.14 |