๐ React์์ useEffect๋ฅผ ํ์ฉํ ๊ทธ๋ํ ์ ๋๋ฉ์ด์ ์ ์ฉ๋ฒ
https://www.loom.com/share/c80fd4298f6d420e9d182623ded33997?sid=56b1c360-1dd4-427a-8208-ac537fe408a0

recharts๋ฅผ ํ์ฉํ์ฌ ๋ง๋ ๊ทธ๋ํ(BarChart)๊ฐ ๋ถ๋๋ฝ๊ฒ ์์์ ์๋๋ก ์์์ค๋ฅด๋ ํจ๊ณผ๋ฅผ ๊ตฌํํฉ๋๋ค.
๐ ํต์ฌ ํฌ์ธํธ๋ ์ด๊ธฐ ๋ฐ์ดํฐ(0 ๊ฐ)๋ฅผ useState๋ก ์ค์ ํ๊ณ , useEffect๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ ์ง์ ์ผ๋ก ์ฆ๊ฐ์ํค๋ ๊ฒ์ ๋๋ค.
๐ ๋ฉ์ปค๋์ฆ์ ์์ด๋์ด ๊ทธ๋ฆฌ๊ณ ์ํฉ
์ ๋ ํ๋ก ํธ ๊ฐ๋ฐ์ ํ๋ฉด์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ถ๋ค์ ์๋ฒ ๋ฐฐํฌ ์ ์๋ ๋ ๋๋ฏธ๋ฐ์ดํฐ๋ฅผ ์จ์ ์๋ฒ api๊ฐ ์์๋ ์ ๊ฐ ๊ตฌํํ ๋์์ ํ ์คํธํ๊ณ ๋ ํ์ต๋๋ค.
๋ณด์ฌ์ง๋ ์๊ฐ์ ๊ทธ๋ํ๊ฐ 0 -> (๊ฐ๊ณ ์๋ ๋ฐ์ดํฐ์ ๊ฐ) ์ผ๋ก ์์นํ๋ ํจ๊ณผ๋ฅผ ์ถ๊ฐํ๋ค๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ํฅ์ํ์ง ์์๊น ์ถ์์ต๋๋ค.
์ด๋ timer ํ์ฉ ๋ฐ zero ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ํ๋ ๋ ์ถ๊ฐํ ํ, zero ๋๋ฏธ ๋ฐ์ดํฐ -> ๋์ ๋ฐ์ดํฐ ๋ณด์ฌ์ฃผ๋ฉด ๋ ๊ฒ ๊ฐ์์ต๋๋ค.
๐ ๊ธฐ๋ํจ๊ณผ
๐น ์ฒ์ ๊ทธ๋ํ๊ฐ ๋ ๋๋ง๋ ๋ ๋ชจ๋ ๋ง๋๊ฐ 0์์ ์์
๐น useEffect๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ฉฐ ์ ๋๋ฉ์ด์ ์ ์ฉ
๐น ๋ถ๋๋ฝ๊ฒ ์์์ ์๋๋ก ์์์ค๋ฅด๋ ๊ทธ๋ํ ๊ตฌํ
๐น ์ฌ์ฉ์ ๊ฒฝํ ์ฆ๊ฐ
๐ฏ ์์ฑ๋ ์ฝ๋ (GraphPage.jsx) <์น์ ํ ์ฃผ์์ ๊ณ๋ค์ธ>
import { useContext, useEffect, useState } from 'react';
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Legend,
CartesianGrid,
Cell,
} from 'recharts';
import { GoalContext } from '../context/GoalContext';
import '../style/GraphPage.scss';
import { SidebarContext } from '../context/SidebarContext';
const GraphPage = () => {
const { goalAmount } = useContext(GoalContext);
const { isSidebarOpen } = useContext(SidebarContext);
// ์ฃผ์ ์๋น ํญ๋ชฉ (๋๋ฏธ ๋ฐ์ดํฐ)
const expenseItems = [
{ category: '์นดํ & ์๋ฃ', item: '์คํ๋ฒ
์ค ๋ผ๋ผ', amount: 5_000 },
{ category: '์์ฌ', item: '์ ์ฌ ์์ฌ (๊น์น์ฐ๊ฐ)', amount: 12_000 },
{ category: '์ํ์ฉํ', item: '์ธ์ ๊ตฌ์
', amount: 8_000 },
{ category: '์ฌ๊ฐ ํ๋', item: '๋ทํ๋ฆญ์ค ๊ตฌ๋
', amount: 14_000 },
{ category: '๊ธฐํ', item: '์ฑ
๊ตฌ์
', amount: 20_000 },
];
const totalSpending = expenseItems.reduce((acc, cur) => acc + cur.amount, 0);
// โ
์ด๊ธฐ ๊ทธ๋ํ ๋ฐ์ดํฐ (๋ชจ๋ ๊ฐ 0)
const [animatedData, setAnimatedData] = useState([
{ label: '์ด ์ง์ถ', value: 0 },
{ label: '์ด ์๋น', value: 0 },
{ label: '๋ชฉํ ๊ธ์ก', value: 0 },
]);
// โ
์ค์ ๊ทธ๋ํ ๋ฐ์ดํฐ
const originalData = [
{ label: '์ด ์ง์ถ', value: totalSpending },
{ label: '์ด ์๋น', value: 40_000 },
{ label: '๋ชฉํ ๊ธ์ก', value: goalAmount || 0 },
];
// โ
`useEffect`๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋๋ฉ์ด์
์ ์ฉ
useEffect(() => {
// 1๏ธโฃ `setTimeout`์ ์ด์ฉํด ๋น๋๊ธฐ์ ์ผ๋ก ์คํ (์ ๋๋ฉ์ด์
ํจ๊ณผ ๋ถ๋๋ฝ๊ฒ)
const timeout = setTimeout(() => {
// 2๏ธโฃ ์ ๋๋ฉ์ด์
์ด ์ ์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธ
setAnimatedData(originalData);
}, 300); // 0.3์ด ํ ์คํ (๋ถ๋๋ฌ์ด ์์ ํจ๊ณผ)
// 3๏ธโฃ `useEffect` ์ ๋ฆฌ ํจ์: ๋ถํ์ํ `setTimeout` ํด๋ฆฌ์ด (๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ)
return () => clearTimeout(timeout);
}, [goalAmount]); // ๐ `goalAmount`๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์คํ
return (
<div className={`graph-wrapper ${isSidebarOpen ? 'sidebar-active' : ''}`}>
<div className="graph-left">
<div className="expense-graph-card">
<h3>์ง์ถ, ์๋น, ๋ชฉํ ๊ธ์ก ๋น๊ต ๊ทธ๋ํ</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={animatedData} barGap={10}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" tick={{ fontSize: 14 }} />
<YAxis tick={{ fontSize: 14 }} />
<Tooltip
contentStyle={{
backgroundColor: 'white',
border: 'none',
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)',
}}
itemStyle={{ color: '#001f5c', fontWeight: 600 }}
/>
<Legend verticalAlign="top" height={36} />
<Bar dataKey="value" fill="#001f5c" radius={[10, 10, 0, 0]}>
{animatedData.map((entry, index) => (
<Cell key={`cell-${index}`} style={{ transition: 'height 1s ease-in-out' }} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
);
};
export default GraphPage;
๐ ํต์ฌ ์ฝ๋ ์ค๋ช (useEffect ์ ๋๋ฉ์ด์ ์๋ฆฌ)
useEffect(() => {
const timeout = setTimeout(() => {
setAnimatedData(originalData); // โ
0์์ ๋ชฉํ ๊ฐ์ผ๋ก ๋ณ๊ฒฝ
}, 300); // 0.3์ด ํ ์คํ (๋ถ๋๋ฌ์ด ์์ ํจ๊ณผ)
return () => clearTimeout(timeout); // โ
๋ถํ์ํ ์คํ ๋ฐฉ์ง (๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ)
}, [goalAmount]); // ๐ `goalAmount` ๋ณ๊ฒฝ ์ ์คํ
โ ํน์๋ ํ๋ ๋ง์์... goalAmount๊ฐ useEffect ์์กด์ฑ ๋ฐฐ์ด์ ํฌํจ๋์ด ์๋๊ฐ? (๊ทธ๋ฆฌ๊ณ goalAmount๋ ๋ฌด์์ธ๊ฐ?)
goalAmount๋ ์ ์ญ ์ํ ๊ด๋ฆฌ(Context API)๋ฅผ ํตํด ๊ฐ์ ธ์ค๋ ์ฌ์ฉ์์ ๋ชฉํ๊ธ์ก์ ๋๋ค.
์ฌ์ฉ์๊ฐ ๋ชฉํ ๊ธ์ก์ ๋ณ๊ฒฝํ๋ฉด ๊ทธ๋ํ๋ ์ ๋ฐ์ดํธ๋์ด์ผ ํฉ๋๋ค.
๋ํ, ์ฌ์ฉ์๊ฐ ๋ชฉํ ๊ธ์ก์ ๋ฐ๊พผ๋ค๋ฉด ๊ทธ์ ๋ฐ๋ผ ๊ทธ๋ํ ์ ๋๋ฉ์ด์ ์ ๋ค์ ์คํํฉ๋๋ค.
๐น ์์ ์๋๋ฆฌ์ค
1. ์ฌ์ฉ์๊ฐ ๋ชฉํ ๊ธ์ก์ 100,000์์์ 200,000์์ผ๋ก ๋ณ๊ฒฝ
2. goalAmount ๊ฐ์ด ๋ฐ๋๋ฉด์ useEffect๊ฐ ์คํ
์ถ๊ฐ) originalData ๋ฐฐ์ด์ด goalAmount๋ฅผ ํฌํจํ๊ณ ์๊ธฐ ๋๋ฌธ
โข originalData๋ ์ต์ข ์ ์ผ๋ก ๊ทธ๋ํ์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์์ต๋๋ค.
โข ํ์ง๋ง originalData ๋ด๋ถ์ goalAmount๊ฐ ํฌํจ๋์ด ์์ผ๋ฏ๋ก,
goalAmount๊ฐ ๋ฐ๋๋ฉด originalData๋ ๋ฐ
const originalData = [
{ label: '์ด ์ง์ถ', value: totalSpending },
{ label: '์ด ์์
', value: 40_000 },
{ label: '๋ชฉํ ๊ธ์ก', value: goalAmount || 0 }, // โ
goalAmount ์ฌ์ฉ
];
โข ๋ง์ฝ ์์กด์ฑ ๋ฐฐ์ด์ goalAmount๋ฅผ ํฌํจํ์ง ์์ผ๋ฉด ๋ชฉํ ๊ธ์ก์ด ๋ณ๊ฒฝ๋์ด๋ ๊ทธ๋ํ๊ฐ ์ ๋ฐ์ดํธ๋์ง ์์ต๋๋ค.
โข ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ๋ชฉํ ๊ธ์ก์ 300,000์ โ 500,000์์ผ๋ก ๋ณ๊ฒฝํ์ ๋,
useEffect๊ฐ ์คํ๋์ง ์์ผ๋ฉด ๊ทธ๋ํ๋ ์ฌ์ ํ 300,000์์ผ๋ก ๋จ์์์ ๊ฒ์ ๋๋ค.
๋ชฉํ ๊ธ์ก ๋ณ๊ฒฝ ์ ์ ๋๋ฉ์ด์ ์ ๋ค์ ์คํํ๊ธฐ ์ํด (useEffect์ ์ฒ ํ)
โข ๊ธฐ์กด ๊ฐ์ด ๋ฐ๋ก ๋ณ๊ฒฝ๋๋ ๊ฒ์ด ์๋๋ผ ์ ๋๋ฉ์ด์ ์ ์ ์ฉํ๋ฉด์ ๋ถ๋๋ฝ๊ฒ ๋ณ๊ฒฝ๋์ด์ผ ํฉ๋๋ค.
โข goalAmount๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค setAnimatedData๊ฐ ์คํ๋๋๋ก ์์กด์ฑ ๋ฐฐ์ด์ ํฌํจํด์ผํฉ๋๋ค
๐ useEffect๋ฅผ ํ์ฉํ ์ ๋๋ฉ์ด์ ๊ตฌํ ๊ณผ์
1๏ธโฃ ์ด๊ธฐ ๊ฐ ์ค์ (useState)
const [animatedData, setAnimatedData] = useState([
{ label: '์ด ์ง์ถ', value: 0 },
{ label: '์ด ์๋น', value: 0 },
{ label: '๋ชฉํ ๊ธ์ก', value: 0 },
]);
โข ๋ชจ๋ ๊ทธ๋ํ์ ๊ฐ์ด 0์์ ์์
โข ๊ทธ๋ํ๊ฐ ํ ๋ฒ์ ๋ํ๋๋ ๊ฒ ์๋๋ผ ์ ์ง์ ์ผ๋ก ์ฆ๊ฐํ๋๋ก ์ค์
2๏ธโฃ useEffect๋ฅผ ํ์ฉํ์ฌ ๋ฐ์ดํฐ ๋ณ๊ฒฝ
useEffect(() => {
const timeout = setTimeout(() => {
setAnimatedData(originalData);
}, 300);
return () => clearTimeout(timeout);
}, [goalAmount]);
๐น ์ด๊ธฐ ๋ฐ์ดํฐ(0) โ ๋ชฉํ ๋ฐ์ดํฐ(์ค์ ๊ฐ)๋ก ๋ณ๊ฒฝ
๐น setTimeout์ ํ์ฉํ์ฌ 0.3์ด ๋ค ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ
๐น return () => clearTimeout(timeout);์ ์ฌ์ฉํ์ฌ ๋ถํ์ํ ์คํ์ ๋ฐฉ์ง
3๏ธโฃ Cell์ ํ์ฉํ ๋ง๋ ๊ทธ๋ํ ์ ๋๋ฉ์ด์
<Bar dataKey="value" fill="#001f5c" radius={[10, 10, 0, 0]}>
{animatedData.map((entry, index) => (
<Cell key={`cell-${index}`} style={{ transition: 'height 1s ease-in-out' }} />
))}
</Bar>
๐น ๊ฐ **๋ง๋(Bar)**์ ๊ฐ๋ณ์ ์ผ๋ก ์ ๋๋ฉ์ด์ ํจ๊ณผ ์ ์ฉ
๐น transition: height 1s ease-in-out;์ ์ถ๊ฐํ์ฌ ๋ถ๋๋ฝ๊ฒ ์ฌ๋ผ์ค๋ ํจ๊ณผ
๐ฏ ๊ฒฐ๊ณผ
๐ฅ ์์ฑ๋ ๊ทธ๋ํ๋ ์ด๊ธฐ์๋ 0์์ ์์ํ ํ, ๋ถ๋๋ฝ๊ฒ ์๋ก ์ฌ๋ผ์ค๋ฉฐ ์ ๋๋ฉ์ด์ ํจ๊ณผ๊ฐ ์ ์ฉ๋ฉ๋๋ค!
โ useEffect๋ฅผ ํ์ฉํ์ฌ ๋ ๋๋ง ํ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
โ setTimeout์ ์ฌ์ฉํ์ฌ ์์ฐ์ค๋ฝ๊ฒ ๋ณํ๋ ๋๋์ ์ค
โ Cell์ transition์ ์ ์ฉํ์ฌ ๊ทธ๋ํ๊ฐ ์๋ก ์์์ค๋ฅด๋ ํจ๊ณผ ์ถ๊ฐ