์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- ์๋ฐ์คํฌ๋ฆฝํธ
- ใทใ
- redux์ํ์ ์ง
- for~in/for~of
- CSS
- User Flow
- ๋ด์ฅ๊ณ ์ฐจํจ์
- UI
- children vs childrenNodes
- Beesbeesbees
- https://dasima.xyz/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%A0%9C%EA%B3%B1-math-pow-%EA%B3%84%EC%82%B0/
- ์๋ฐ์คํฌ๋ฆฝํธ#์กฐ๊ฑด๋ฌธ#๋ฌธ์์ด
- js
- variable#function
- slice/splice/split
- cmarket
- dom
- https://developer-talk.tistory.com/299
- toString#String
- JS#3์ผ์ฐจ๋ฌ๋ฆฌ์#์ด๋ฐ์ธ๋ฐ#์๊ฐ๊ธ๋ฐฉ~
- @redux-toolkit
- https://www.daleseo.com/js-array-slice-splice/
- react
- removeCookie
- ์๋ฐ์คํฌ๋ฆฝํธ#JS#slice#splice
- ํท๊ฐ๋ฆฐ๋ค~
- ๋ ธ๋๊ต๊ณผ์
- ์๋ฐ์คํฌ๋ฆฝํธ#JS#var#let#const#undefined#null
- https://lo-victoria.com/introduction-to-redux-toolkit-for-beginners
- UX
- Today
- Total
Daily Front_Minhhk
[React] React Custom Component ๋ณธ๋ฌธ
๐ ์ ์ ๋์ด๋๊ฐ ์ด๋ ค์์ง๊ณ ์๋ค,,, ์ ํ๊ณ ์๋๊ฑด์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง!
๐ styled-components์ ์์ฑ์ ์ผํญ์ฐ์ฐ์๋ก ๋ํ๋ด์๋ค
Bare minimum Requirement
modal.js
>
import { useState } from "react";
import styled from "styled-components";
export const ModalContainer = styled.div`
// TODO : Modal์ ๊ตฌํํ๋๋ฐ ์ ์ฒด์ ์ผ๋ก ํ์ํ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`;
export const ModalBackdrop = styled.div`
// TODO : Modal์ด ๋ด์ ๋์ ๋ฐฐ๊ฒฝ์ ๊น์์ฃผ๋ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
background-color: rgba(0, 0, 0, 0.3);
z-index: 333;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
export const ModalBtn = styled.button`
background-color: var(--coz-purple-600);
text-decoration: none;
border: none;
padding: 20px;
color: white;
border-radius: 30px;
cursor: grab;
`;
export const ModalView = styled.div.attrs((props) => ({
// attrs ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์๋์ ๊ฐ์ด div ์๋ฆฌ๋จผํธ์ ์์ฑ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
role: "dialog",
}))`
// TODO : Modal์ฐฝ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
width: 500px;
height: 300px;
background: white;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 10px;
> div.text {
font-size: 30px;
margin: 20px;
color: violet;
}
`;
// Exitbtn ์์ฑ -> css
export const Exitbtn = styled.button`
background-color: white;
color: black;
padding: 10px 15px;
font-size: 15px;
margin: 10px;
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = () => {
// TODO : isOpen์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํฉ๋๋ค.
setIsOpen(!isOpen);
};
return (
<>
<ModalContainer onClick={openModalHandler}>
<ModalBtn
// TODO : ํด๋ฆญํ๋ฉด Modal์ด ์ด๋ฆฐ ์ํ(isOpen)๋ฅผ boolean ํ์
์ผ๋ก ๋ณ๊ฒฝํ๋ ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค.
onClick={openModalHandler}
>
{/* TODO : ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ํ์ฉํด์ Modal์ด ์ด๋ฆฐ ์ํ(isOpen์ด true์ธ ์ํ)์ผ ๋๋ ModalBtn์ ๋ด๋ถ ํ
์คํธ๊ฐ 'Opened!' ๋ก Modal์ด ๋ซํ ์ํ(isOpen์ด false์ธ ์ํ)์ผ ๋๋ ModalBtn ์ ๋ด๋ถ ํ
์คํธ๊ฐ 'Open Modal'์ด ๋๋๋ก ๊ตฌํํด์ผ ํฉ๋๋ค. */}
{isOpen ? "Opened!" : "Open Modal"}
</ModalBtn>
{/* TODO : ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ํ์ฉํด์ Modal์ด ์ด๋ฆฐ ์ํ(isOpen์ด true์ธ ์ํ)์ผ ๋๋ง ๋ชจ๋ฌ์ฐฝ๊ณผ ๋ฐฐ๊ฒฝ์ด ๋ฐ ์ ์๊ฒ ๊ตฌํํด์ผ ํฉ๋๋ค. */}
{isOpen === false ? null : (
<ModalBackdrop>
{/* ๋ชจ๋ฌ์ฐฝ์ ํด๋ฆญ ํด๋ ์ฐฝ์ด ๊บผ์ง์ง ์์,, e => e.stopPropagation()*/}
<ModalView onClick={(e) => e.stopPropagation()}>
<Exitbtn onClick={openModalHandler}>X</Exitbtn>
<div className="text">Hello CodeStates</div>
</ModalView>
</ModalBackdrop>
)}
</ModalContainer>
</>
);
};
<ModalBackdrop>๊ณผ <Exitbtn>์ ํด๋ฆญํ๋ฉด
<ModalView>๊ฐ ๋ํ๋๋ค.
<ModalView>๋ฅผ ๋ซ์ ๋ <ModalBackdrop>๊ณผ <Exitbtn>์ ํด๋ฆญํด์ผ ๋ซํ์ผ ํ๋๋ฐ
<ModalView>๋ฅผ ๋๋ฌ๋ ๋ซํ๋ค.
๋ถ๋ชจ ์๋ฆฌ๋จผํธ์๊ฒ๋ ์ด๋ฒคํธ๊ฐ ์ ํ๋์ง ์๊ธฐ ์ํด e.stopPropagation์ ์ฌ์ฉํ๋ค.
e.preventDefault๋ ๊ณ ์ ๋์์ ์ค๋จ์ํค๊ณ ,
e.stopPropagation๋ ์์ ์๋ฆฌ๋จผํธ๋ค๋ก์ ์ด๋ฒคํธ ์ ํ๋ฅผ ์ค๋จ์ํจ๋ค.
๐ฅ e.stopPropagation()
e.preventDefault()
html ์์ a ํ๊ทธ๋ submit ํ๊ทธ๋ ๊ณ ์ ์ ๋์์ด ์๋ค.
ํ์ด์ง๋ฅผ ์ด๋์ํจ๋ค๊ฑฐ๋ form ์์ ์๋ input ๋ฑ์ ์ ์กํ๋ค๋๊ฐ ๊ทธ๋ฌํ ๋์์ด ์๋๋ฐ
e.preventDefault ๋ ๊ทธ ๋์์ ์ค๋จ์ํจ๋ค.
Toggle.js
>
import { useState } from "react";
import styled from "styled-components";
const ToggleContainer = styled.div`
position: relative;
margin-top: 8rem;
left: 47%;
cursor: pointer;
> .toggle-container {
width: 50px;
height: 24px;
border-radius: 30px;
background-color: #8b8b8b;
// TODO : .toggle--checked ํด๋์ค๊ฐ ํ์ฑํ ๋์์ ๊ฒฝ์ฐ์ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
}
> .toggle--checked {
background-color: purple;
}
> .toggle-circle {
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border-radius: 50%;
background-color: #ffffff;
transition: 0.5s;
// TODO : .toggle--checked ํด๋์ค๊ฐ ํ์ฑํ ๋์์ ๊ฒฝ์ฐ์ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
}
> .toggle--checked {
left: 27px;
transition: 0.5s;
}
`;
const Desc = styled.div`
// TODO : ์ค๋ช
๋ถ๋ถ์ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
display: flex;
justify-content: center;
`;
export const Toggle = () => {
const [isOn, setIsOn] = useState(false);
const toggleHandler = () => {
// TODO : isOn์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฉ์๋๋ฅผ ๊ตฌํํฉ๋๋ค.
setIsOn(!isOn);
};
return (
<>
<ToggleContainer
// TODO : ํด๋ฆญํ๋ฉด ํ ๊ธ์ด ์ผ์ง ์ํ(isOn)๋ฅผ boolean ํ์
์ผ๋ก ๋ณ๊ฒฝํ๋ ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค.
>
{/* TODO : ์๋์ div ์๋ฆฌ๋จผํธ 2๊ฐ๊ฐ ์์ต๋๋ค. ๊ฐ๊ฐ์ ํด๋์ค๋ฅผ 'toggle-container', 'toggle-circle' ๋ก ์ง์ ํ์ธ์. */}
{/* TIP : Toggle Switch๊ฐ ON์ธ ์ํ์ผ ๊ฒฝ์ฐ์๋ง toggle--checked ํด๋์ค๋ฅผ div ์๋ฆฌ๋จผํธ 2๊ฐ์ ๋ชจ๋ ์ถ๊ฐํฉ๋๋ค. ์กฐ๊ฑด๋ถ ์คํ์ผ๋ง์ ํ์ฉํ์ธ์. */}
<div
//! ํ ๊ธ ์ปจํ
์ด๋์ Circle ๋ฒํผ ํด๋ฆญ ์ on/off
onClick={toggleHandler}
className={`toggle-container ${isOn ? "toggle--checked" : ""}`}
/>
<div
//! ํ ๊ธ ์ปจํ
์ด๋์ Circle ๋ฒํผ ํด๋ฆญ ์ on/off
onClick={toggleHandler}
className={`toggle-circle ${isOn ? "toggle--checked" : ""}`}
/>
</ToggleContainer>
{/* TODO : Desc ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํด์ผ ํฉ๋๋ค. */}
{/* TIP: Toggle Switch๊ฐ ON์ธ ์ํ์ผ ๊ฒฝ์ฐ์ Desc ์ปดํฌ๋ํธ ๋ด๋ถ์ ํ
์คํธ๋ฅผ 'Toggle Switch ON'์ผ๋ก, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ 'Toggle Switch OFF'๊ฐ ๋ฉ๋๋ค. ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ํ์ฉํ์ธ์. */}
{isOn === false ? (
<Desc>Toggle Switch OFF</Desc>
) : (
<Desc>Toggle Switch ON</Desc>
)}
</>
);
};
Tab.js
>
import { useState } from "react";
import styled from "styled-components";
// TODO: Styled-Component ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด TabMenu ์ Desc ์ปดํฌ๋ํธ์ CSS๋ฅผ ๊ตฌํํฉ๋๋ค.
const TabMenu = styled.ul`
background-color: #dcdcdc;
color: rgba(73, 73, 73, 0.5);
font-weight: bold;
display: flex;
flex-direction: row;
justify-items: center;
align-items: center;
list-style: none;
margin-bottom: 7rem;
.submenu {
${"" /* ๊ธฐ๋ณธ Tabmenu ์ ๋ํ CSS๋ฅผ ๊ตฌํํฉ๋๋ค. */}
display: flex;
justify-content: space-around;
align-items: center;
width: 230px;
height: 40px;
}
.focused {
${"" /* ์ ํ๋ Tabmenu ์๋ง ์ ์ฉ๋๋ CSS๋ฅผ ๊ตฌํํฉ๋๋ค. */}
background-color: blueviolet;
color: #ffffff;
}
& div.desc {
text-align: center;
}
`;
const Desc = styled.div`
text-align: center;
`;
export const Tab = () => {
// TIP: Tab Menu ์ค ํ์ฌ ์ด๋ค Tab์ด ์ ํ๋์ด ์๋์ง ํ์ธํ๊ธฐ ์ํ
// currentTab ์ํ์ currentTab์ ๊ฐฑ์ ํ๋ ํจ์๊ฐ ์กด์ฌํด์ผ ํ๊ณ , ์ด๊ธฐ๊ฐ์ 0 ์
๋๋ค.
const [currentTab, setCurrentTab] = useState(0);
const menuArr = [
{ name: "Tab1", content: "Tab menu ONE" },
{ name: "Tab2", content: "Tab menu TWO" },
{ name: "Tab3", content: "Tab menu THREE" },
];
const selectMenuHandler = (index) => {
// TIP: parameter๋ก ํ์ฌ ์ ํํ ์ธ๋ฑ์ค ๊ฐ์ ์ ๋ฌํด์ผ ํ๋ฉฐ, ์ด๋ฒคํธ ๊ฐ์ฒด(event)๋ ์ฐ์ง ์์ต๋๋ค
// TODO : ํด๋น ํจ์๊ฐ ์คํ๋๋ฉด ํ์ฌ ์ ํ๋ Tab Menu ๊ฐ ๊ฐฑ์ ๋๋๋ก ํจ์๋ฅผ ์์ฑํ์ธ์.
setCurrentTab(index);
};
return (
<>
<div>
<TabMenu>
{/*TODO: ์๋ ํ๋์ฝ๋ฉ๋ ๋ด์ฉ ๋์ ์, map์ ์ด์ฉํ ๋ฐ๋ณต์ผ๋ก ์ฝ๋๋ฅผ ์์ ํฉ๋๋ค.*/}
{/*TIP: li ์๋ฆฌ๋จผํธ์ class๋ช
์ ๊ฒฝ์ฐ ์ ํ๋ tab ์ 'submenu focused' ๊ฐ ๋๋ฉฐ,
๋๋จธ์ง 2๊ฐ์ tab์ 'submenu' ๊ฐ ๋ฉ๋๋ค.*/}
{menuArr.map((item, index) => {
return (
<>
<li
key={index}
className={currentTab === index ? "submenu focused" : "submenu"}
onClick={() => {selectMenuHandler(index)}}
>
{item.name}
</li>
</>
);
})}
</TabMenu>
<Desc>
{/*TODO: ์๋ ํ๋์ฝ๋ฉ๋ ๋ด์ฉ ๋์ ์, ํ์ฌ ์ ํ๋ ๋ฉ๋ด ๋ฐ๋ฅธ content๋ฅผ ํ์ํ์ธ์*/}
<p>{menuArr[currentTab].content}</p>
</Desc>
</div>
</>
);
};
Tag.js
>
import { useState } from "react";
import styled from "styled-components";
// TODO: Styled-Component ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด ์ฌ๋ฌ๋ถ๋ง์ tag ๋ฅผ ์์ ๋กญ๊ฒ ๊พธ๋ฉฐ ๋ณด์ธ์!
export const TagsInput = styled.div`
margin: 8rem auto;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
min-height: 48px;
width: 480px;
padding: 0 8px;
border: 1px solid rgb(214, 216, 218);
border-radius: 6px;
> ul {
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 8px 0 0 0;
> .tag {
width: auto;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
padding: 0 8px;
font-size: 14px;
list-style: none;
border-radius: 6px;
margin: 0 8px 8px 0;
background: var(--coz-purple-600);
> .tag-close-icon {
display: block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
font-size: 14px;
margin-left: 8px;
color: var(--coz-purple-600);
border-radius: 50%;
background: #fff;
cursor: pointer;
}
}
}
> input {
flex: 1;
border: none;
height: 46px;
font-size: 14px;
padding: 4px 0 0 0;
:focus {
outline: transparent;
}
}
&:focus-within {
border: 1px solid var(--coz-purple-600);
}
`;
export const Tag = () => {
const initialTags = ["CodeStates", "kimcoding"];
const [tags, setTags] = useState(initialTags);
const removeTags = (indexToRemove) => {
// TODO : ํ๊ทธ๋ฅผ ์ญ์ ํ๋ ๋ฉ์๋๋ฅผ ์์ฑํ์ธ์. filter() ์ฌ์ฉ
const removeMethod = tags.filter((el, item) => {
return item !== indexToRemove;
});
setTags(removeMethod);
};
const addTags = (event) => {
// TODO : tags ๋ฐฐ์ด์ ์๋ก์ด ํ๊ทธ๋ฅผ ์ถ๊ฐํ๋ ๋ฉ์๋๋ฅผ ์์ฑํ์ธ์.
// ์ด ๋ฉ์๋๋ ํ๊ทธ ์ถ๊ฐ ์ธ์๋ ์๋ 3 ๊ฐ์ง ๊ธฐ๋ฅ์ ์ํํ ์ ์์ด์ผ ํฉ๋๋ค.
// - ์ด๋ฏธ ์
๋ ฅ๋์ด ์๋ ํ๊ทธ์ธ์ง ๊ฒ์ฌํ์ฌ ์ด๋ฏธ ์๋ ํ๊ทธ๋ผ๋ฉด ์ถ๊ฐํ์ง ๋ง๊ธฐ
// - ์๋ฌด๊ฒ๋ ์
๋ ฅํ์ง ์์ ์ฑ Enter ํค ์
๋ ฅ์ ๋ฉ์๋ ์คํํ์ง ๋ง๊ธฐ
const tagValue = event.target.value;
if (
tagValue !== "" &&
event.key === "Enter" &&
// ํน์ ๊ฐ ํฌํจ ํ์ธ ์, indexOf(), includes() ์ฌ์ฉ
!tags.includes(tagValue)
) {
// ์
๋ ฅ๊ฐ ์ถ๊ฐ
setTags([...tags, tagValue]);
// - ํ๊ทธ๊ฐ ์ถ๊ฐ๋๋ฉด input ์ฐฝ ๋น์ฐ๊ธฐ
event.target.value = "";
}
};
return (
<>
<TagsInput>
<ul id="tags">
{tags.map((tag, index) => (
<li key={index} className="tag">
<span className="tag-title">{tag}</span>
<span
className="tag-close-icon"
onClick={() => removeTags(index)}
>
x
{/* TODO : tag-close-icon์ด tag-title ์ค๋ฅธ์ชฝ์ x ๋ก ํ์๋๋๋ก ํ๊ณ ,
์ญ์ ์์ด์ฝ์ click ํ์ ๋ removeTags ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค. */}
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
/* ํค๋ณด๋์ Enter ํค์ ์ํด addTags ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค. */
onKeyUp={(event) => {
addTags(event);
}}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};
Advanced Challenge (Optional)
ClickToEdit.js
>
import { useEffect, useState, useRef } from 'react';
import styled from 'styled-components';
export const InputBox = styled.div`
text-align: center;
display: inline-block;
width: 150px;
height: 30px;
border: 1px #bbb dashed;
border-radius: 10px;
margin-left: 1rem;
`;
export const InputEdit = styled.input`
text-align: center;
display: inline-block;
width: 150px;
height: 30px;
`;
export const InputView = styled.div`
text-align: center;
align-items: center;
margin-top: 3rem;
div.view {
margin-top: 3rem;
}
`;
export const MyInput = ({ value, handleValueChange }) => {
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false);
const [newValue, setNewValue] = useState(value);
// ์ฒซ ๋ ๋๋ง + [] ๋ฐ๋ ๋ ๋ ๋๋ง
useEffect(() => {
if (isEditMode) {
inputEl.current.focus();
}
}, [isEditMode]);
useEffect(() => {
setNewValue(value);
}, [value]);
const handleClick = () => {
// TODO : isEditMode ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
setEditMode(true)
};
const handleBlur = () => {
// TODO : Edit๊ฐ ๋ถ๊ฐ๋ฅํ ์ํ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
setEditMode(false)
handleValueChange(newValue);
};
const handleInputChange = (e) => {
// TODO : ์ ์ฅ๋ value๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
//? input ์
๋ ฅ๊ฐ newValue์ ์
๋ฐ์ดํธ
setNewValue(e.target.value)
};
return (
<InputBox>
{isEditMode ? (
<InputEdit
type='text'
value={newValue}
ref={inputEl}
// TODO : ํฌ์ปค์ค๋ฅผ ์์ผ๋ฉด Edit๊ฐ ๋ถ๊ฐ๋ฅํ ์ํ๋ก ๋ณ๊ฒฝ๋๋ ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค.
onBlur={handleBlur}
// TODO : ๋ณ๊ฒฝ ์ฌํญ์ด ๊ฐ์ง๋๋ฉด ์ ์ฅ๋ value๋ฅผ ์
๋ฐ์ดํธ ๋๋ ๋ฉ์๋๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค.
onChange={handleInputChange}
/>
) : (
<span
// TODO : ํด๋ฆญํ๋ฉด Edit๊ฐ ๊ฐ๋ฅํ ์ํ๋ก ๋ณ๊ฒฝ๋์ด์ผ ํฉ๋๋ค.
onClick={handleClick}
>{newValue}</span>
)}
</InputBox>
);
}
const cache = {
name: '๊น์ฝ๋ฉ',
age: 20
};
export const ClickToEdit = () => {
const [name, setName] = useState(cache.name);
const [age, setAge] = useState(cache.age);
return (
<>
<InputView>
<label>์ด๋ฆ</label>
<MyInput value={name} handleValueChange={(newValue) => setName(newValue)} />
</InputView>
<InputView>
<label>๋์ด</label>
<MyInput value={age} handleValueChange={(newValue) => setAge(newValue)} />
</InputView>
<InputView>
<div className='view'>์ด๋ฆ {name} ๋์ด {age}</div>
</InputView>
</>
);
};
onBlur = {handleBlur}
onBlur : ํฌ์ปค์ค๊ฐ ํด์ง๋ ๋ ์ด๋ฒคํธ ์ค์
<span>์ ํด๋ฆญํ์ ๋, ์ฐ๋ฆฌ๋ <InputEdit>์์ ๊ธ์ ์์ ํ ์ ์๋ค.
๋ค์ <span>์ ํด๋ฆญํด ๊ธ์ ์ ์ผ๋ ค๊ณ ํ์ ๋, ๋ค์ <span>์ผ๋ก ๋์๊ฐ์ผํ๋ค.
์ด๋ onBlur ์ด๋ฒคํธ๋ฅผ ํตํด ์ค์ ํ ์ ์๋ค.
onBlur ์ด๋ฒคํธ๋ฅผ ์ฐ์ง ์์ผ๋ฉด, ๊ทธ๋๋ก <InputEdit>์ ๋จธ๋ฌผ๋ฌ ์๊ฒ ๋๋ค.
'Code๊ฐ๋ฐ์ผ์ง' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Redux] Store, Reducer, Action, Dispatch // Hooks : useDispatch, useSelector (0) | 2022.12.29 |
---|---|
[React] Props Drilling + Cmarket Hooks (0) | 2022.12.27 |
[React] Custom Component (0) | 2022.12.22 |
[Figma] ๋จ์ถํค,, ๋น๊ทผ๋ง์ผ ์ฑ lo-fi clone (0) | 2022.12.21 |
UI UX (0) | 2022.12.19 |