Jieunny์˜ ๋ธ”๋กœ๊ทธ

S3) Unit 8. [์‹ค์Šต] Coz'Mini Hackaton (Todo-List) ๋ณธ๋ฌธ

CodeStates/Training

S3) Unit 8. [์‹ค์Šต] Coz'Mini Hackaton (Todo-List)

Jieunny 2023. 3. 10. 15:39

๐Ÿ“ฃ  ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ & ๋ฆฌ๋•์Šค & ๋ฆฌ์•กํŠธ ๋ผ์šฐํ„ฐ๋ฅผ ํ™œ์šฉํ•œ ํˆฌ๋‘ ๋ฆฌ์ŠคํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿญ. ํŒŒ์ผ ๊ตฌ์กฐ

src

   ใ„ด components (์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ)

        ใ„ด TodoList.js : TodoItem์„ ๋ชจ์•„ ๋…ผ TotoList

        ใ„ด TodoItem.js : ํ•˜๋‚˜์˜ TodoItem

        ใ„ด TodoInsert.js : Todo ์ž…๋ ฅํ•˜๋Š” ์นธ

        ใ„ด MemoList.js : MemoItem์„ ๋ชจ์•„ ๋…ผ MemoList

        ใ„ด MemoItem.js : ํ•˜๋‚˜์˜ MemoItem

        ใ„ด MemoInsert.js : Memo ์ž…๋ ฅํ•˜๋Š” ์นธ

 

   ใ„ด pages ( ๋ผ์šฐํ„ฐ๋ฅผ ํ†ตํ•ด ์ด๋™ํ•  ํŽ˜์ด์ง€)

        ใ„ด TodoTemplate.js : ์ฒ˜์Œ ํŽ˜์ด์ง€ ์—ด์—ˆ์„ ๋•Œ ๋œจ๋Š” ํ™”๋ฉด(Menu ํ† ๊ธ€, ํ–„์Šคํ„ฐ ๋ณด๊ธฐ ์ฐฝ๋„ ์—ฌ๊ธฐ์„œ ๋„์šด๋‹ค)

        ใ„ด MemoTemplate.js : Header์—์„œ ๋ฉ”๋ชจ ์•„์ด์ฝ˜ ๋ˆ„๋ฅด๋ฉด ์ด๋™ํ•˜๋Š” ํ™”๋ฉด

        ใ„ด Ctreator.js : ์ฒ˜์Œ ํŽ˜์ด์ง€์—์„œ ๋ฉ”๋‰ด ์•„์ด์ฝ˜ -> ์ œ์ž‘์ž ๋ˆ„๋ฅด๋ฉด ์ด๋™ํ•˜๋Š” ํ™”๋ฉด

 

   ใ„ด reducer ( ์•ก์…˜, ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜)

        ใ„ด Todo.js : TodoList์— ํ•„์š”ํ•œ ์•ก์…˜, ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜ ์ •์˜

        ใ„ด Memo.js : MemoList์— ํ•„์š”ํ•œ ์•ก์…˜, ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜ ์ •์˜


๐Ÿฎ. ํ”ผ๊ทธ๋งˆ ์‹œ์—ฐํ™”๋ฉด

โžฐ ํ”ผ๊ทธ๋งˆ๋กœ ์ž…๋ ฅ๊ฐ’ ๋ฐ”๊พธ๋Š” ๊ฑด ์–ด๋–ป๊ฒŒ ํ•˜๋Š” ๊ฑธ๊นŒ..


๐Ÿฏ. ์ฝ”๋“œ ๊ตฌํ˜„ (์ค‘์š”ํ•˜๊ฑฐ๋‚˜ ๊ตฌํ˜„ํ•  ๋•Œ ์–ด๋ ค์› ๋˜ ์ฝ”๋“œ๋งŒ)

− Todo๋ž‘ Memo๊ฐ€ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ๊ฐ™์œผ๋ฏ€๋กœ Todo๋งŒ ์„ค๋ช…

 

๐Ÿ“Œ App.js

import TodoTemplate from './pages/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import styled, { createGlobalStyle } from 'styled-components';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import MemoInsert from './components/MemoInsert';
import MemoTemplate from './pages/MemoTemplate';
import Creator from './pages/Creator';
import { AnimatePresence } from "framer-motion";

const GlobalStyle = createGlobalStyle`
  * {
    box-sizing: border-box;
    font-family: 'Jua', sans-serif;
  }
`;

const AppContainer = styled.div`
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 1px solid black;
`

function App() {
  return (
    <Router>
      <AppContainer>
        <GlobalStyle />    
        <AnimatePresence> 
          <Routes>
            <Route path="/" element={
              <TodoTemplate>
                <TodoInsert />
                <TodoList />
                <MemoInsert />
              </TodoTemplate>
            } />
            <Route path="/memolist" element={<MemoTemplate />} />
            <Route path="/creator" element={<Creator />} />
          </Routes>
        </AnimatePresence>   
      </AppContainer>
    </Router>
  );
}

export default App;

โžฐ GlobalStyle์„ ์‚ฌ์šฉํ•ด์„œ box-sizing์†์„ฑ์„ ๋ชจ๋“  ์š”์†Œ์— ์ ์šฉํ•ด์ฃผ๊ณ , ๊ธ€๊ผด๋„ ๋ฐ”๊ฟ”์ค€๋‹ค.

โžฐ ํŽ˜์ด์ง€ ์ „ํ™˜์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋˜๋Š” motion์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํŽ˜์ด์ง€ ์ด๋™์ด ์ผ์–ด๋‚˜๋Š” <Routes> ๋ถ€๋ถ„์„ 'framer-motion' ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ <AnimatePresence>๋กœ ๊ฐ์‹ธ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

โžฐ ํŽ˜์ด์ง€ ์ „ํ™˜์ด ์ผ์–ด๋‚˜๋Š” ํŒŒ์ผ(์ปดํฌ๋„ŒํŠธ)์€ <Route path="">๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฏธ๋ฆฌ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

 

๐Ÿ“Œ Todo.js 

// ์•ก์…˜, ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜์ธ ๋ฆฌ๋“€์„œ 
// ์•ก์…˜ -> dispatch() -> ๋ฆฌ๋“€์„œ -> store ์ƒ์„ฑ
// ๋‚ด๊ฐ€ ์•ก์…˜์„ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ dispatch()๋กœ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.

// ์•ก์…˜ ํƒ€์ž… ์ •์˜
const TODO_INSERT = "TODO/INSERT";
const TODO_REMOVE = "TODO/REMOVE";
const TODO_UPDATE = "TODO/UPDATE";
const TODO_TOGGLE = "TODO/TOGGLE";
const TODO_RESET = "TODO/RESET";


// ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜ ์ •์˜(์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์•„์˜จ ์ธ์ž๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•œ๋‹ค)
export const todoInsert = (id, text) => {
  return {
    type: TODO_INSERT,
    payload: {
      id: id,
      text: text,
      isCompleted: false,
    },
  };
};

// Remove, Update, Toggle๋„ ๊ฐ™์€ ์›๋ฆฌ


// ์ดˆ๊นƒ๊ฐ’ ์ •์˜
const initState = {
  todos: [
    {
      id: 1,
      text: "๋๋‚ด์ฃผ๊ฒŒ ์ˆจ์‰ฌ๊ธฐ",
      isCompleted: true,
    },
    {
      id: 2,
      text: "๊ฐ„์ง€๋‚˜๊ฒŒ ์ž๊ธฐ",
      isCompleted: false,
    },
    {
      id: 3,
      text: "์ž‘์‚ด๋‚˜๊ฒŒ ๋ฐฅ๋จน๊ธฐ",
      isCompleted: false,
    },
  ],
  
};

// ๋ฆฌ๋“€์„œ ์ƒ์„ฑ(์•ก์…˜์„ ๋ฆฌ๋“€์„œ์— ์ „๋‹ฌํ•˜๊ณ , ๋ฆฌ๋“€์„œ๊ฐ€ ์ด๋ฅผ ๋ณด๊ณ  ์Šคํ† ์–ด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค)
// ์•ก์…˜์„ ๋ฆฌ๋“€์„œ์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” dispatch() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
export default function todoReducer(state = initState, { type, payload }) {
  switch (type) {

    case TODO_INSERT:
      return {
        ...state,
        todos: state.todos.concat({
          id: payload.id,
          text: payload.text,
          isCompleted: false,
        }),
      };

    case TODO_REMOVE:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== payload.id),
      };

    case TODO_UPDATE:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === payload.id ? { ...todo, text: payload.text } : todo
          // todoUpdate๋Š” ์—…๋ฐ์ดํŠธ ํ•  text๋ฅผ ๋ฐ›์•„์˜ค๋ฏ€๋กœ text๋งŒ ๋ฐ”๊ฟ”์ค€๋‹ค.
        ),
      };

    case TODO_TOGGLE:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === payload.id
            ? { ...todo, isCompleted: !todo.isCompleted }
            : todo
        ),
      };

    default:
      return { ...state };
  }
}

 

๐Ÿ“Œ TodoTemplate.js 

import { motion } from "framer-motion";

.hide-menu{
    //...
  }

  .show-menu{
    position: absolute;
    width: 100%;
    height: 80%;
    background-color: #F5EBE0;
    margin: 0;
    border-radius: 15px 15px 0 0;
    padding: 1rem;
    bottom: 0;
    left: 0;
    transition: 1s;
    box-shadow: rgba(17, 17, 26, 0.1) 0px 1px 0px, rgba(17, 17, 26, 0.1) 0px 8px 24px, rgba(17, 17, 26, 0.1) 0px 16px 48px;
    }
  // ๋ฉ”๋‰ด ๋ฒ„ํŠผ ๋ˆ„๋ฅด๊ธฐ ์ „์—” ์ˆจ๊ฒจ๋†“๊ณ , ๋ˆ„๋ฅด๋ฉด ์™ผ->์˜ค ๋กœ ๋‚˜ํƒ€๋‚˜๊ฒŒ ํ•œ๋‹ค.
  
  function TodoTemplate({children}){
  let today = new Date();
  let todayStr = today.toISOString().slice(0, 10);
  const week = ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '];
  let todayWeek = week[today.getDay()];
  const todos = useSelector((state) => state.todoReducer.todos);
  let completedTodo = todos.filter((todo) => todo.isCompleted);
  const [menuOn, setMenuOn] = useState(false);
  const [imgOn, setImgOn] = useState(false);

  const toggleMenu = () => {
    setMenuOn(!menuOn);
  }

  const toggleImg = () => {
    setImgOn(!imgOn);
  }

  return(
    <motion.div
        initial={{opacity: 0}}
        animate={{opacity: 1}}
        exit={{opacity: 0}}
        >

      <Template>
      <Header>
        <StyledLink to="/memolist"><TbNotes className="memo" size="30" color="#1B1A17"/></StyledLink>
      	// react-router์˜ Link๋กœ ๋ฉ”๋ชจ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ฉ”๋ชจ๋“ค์ด ์ €์žฅ๋œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.
      </Header>
      <Title> ์˜ค๋Š˜์˜ ํ•  ์ผ </Title>
      <CompletedTodo>
        ...
      </CompletedTodo>
      {children}
          <ul className={menuOn ? 'show-menu': 'hide-menu'}>
            <StyledLink to="/creator"><MenuLi><MdPeopleOutline className="icon" size="40" color="#1B1A17" />์ œ์ž‘์ž</MenuLi></StyledLink>
            // ์—ฌ๊ธฐ๋„ Link๋ฅผ ์ด์šฉํ•ด์„œ ๋ˆ„๋ฅด๋ฉด ์ œ์ž‘์ž ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.
          </ul>
          { imgOn ? 
              <motion.div
              initial={{opacity: 0}}
              animate={{opacity: 1}}
              exit={{opacity: 0}}
              >
            <ImgContainer onClick={toggleImg}>
              <img src={cinammon} className="cinnamon" alt="ํฌ๊ธฐ๊ฐ€ ๋ณ€ํ•˜๋Š” ํ–„์Šคํ„ฐ"></img>
            </ImgContainer>
              </motion.div>
            :
            null
          }
    </Template>

    </motion.div>
  )
}

โžฐ Menu ํ† ๊ธ€์„ styled-component์˜ props๋ฅผ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ฉด ๋‚˜ํƒ€๋‚˜๊ธฐ๋Š” ํ•˜๋Š”๋ฐ ํŽผ์ณ์ง€๋Š” ๊ฒƒ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ์ค„ ์ˆ˜๊ฐ€ ์—†์–ด์„œ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ณ , ํ‰์†Œ๋Œ€๋กœ class๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค.

โžฐ 'framer-motion' ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ motion์„ ์‚ฌ์šฉํ•ด์„œ ๋ฆฌ์•กํŠธ ๋ผ์šฐํ„ฐ๋กœ ํŽ˜์ด์ง€๊ฐ€ ์ด๋™ํ•  ๋•Œ, ํ•œ๋ฒˆ์— ๋”ฑ ์ผœ์ง€๋Š”๊ฒŒ ์•„๋‹Œ opacity๋ฅผ ์กฐ์ •ํ•ด์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํŽ˜์ด์ง€ ์ „ํ™˜์ด ๋˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

โžฐ ๋ฉ”๋‰ด์—์„œ 'ํ–„์Šคํ„ฐ ๋ณด๊ธฐ' ํƒญ์„ ๋ˆ„๋ฅด๋ฉด imgOn์ด true๋กœ ๋ฐ”๋€Œ๋ฉฐ ํ–„์Šคํ„ฐ ์‚ฌ์ง„์ด ์˜ค๋ฒ„๋ ˆ์ด๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค.

 

๐Ÿ“Œ TodoInsert.js

function TodoInsert(){
  const [todoInput, setTodoInput] = useState('');
  let nextId = useRef(4); // ๋ Œ๋”๋ง์ด ๊ณ„์† ๋˜๋ฉด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— useRef์‚ฌ์šฉ
  // id ๊ฐ’์ด ๊ณ„์† ++ ๋˜์•ผ ํ•˜๋Š”๋ฐ useRef๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ๊ณ„์† ๋ Œ๋”๋ง ๋˜์„œ ์ดˆ๊ธฐํ™”๋œ๋‹ค.

  const dispatch = useDispatch();
  // ๋””์ŠคํŒจ์น˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” Hooks

  //...

  const addTodo = () => {
    if(todoInput.length === 0){
      alert("ํ•  ์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!");
      return;
    }

    dispatch(todoInsert(nextId.current, todoInput));
    // ์•ก์…˜์„ dispatch๋ฅผ ํ†ตํ•ด ๋ฆฌ๋“€์„œ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
    // ์ถ”๊ฐ€ ๋  ํ•  ์ผ์˜ id์™€ ๋‚ด๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’(ํ•  ์ผ ํ…์ŠคํŠธ)์„ ์ „๋‹ฌํ•œ๋‹ค.
    nextId.current += 1; // ๋‹ค์Œ์— ๋“ค์–ด์˜ฌ ํ•  ์ผ์„ ์œ„ํ•ด์„œ id+1 ํ•ด์ค€๋‹ค. 
    onRemove();  
  } 

  return(
    ...
  )

}

โžฐ dispatch๋ฅผ ์‚ฌ์šฉํ•ด์„œ Todo.js์— ์ •์˜ํ•ด ๋‘์—ˆ๋˜ ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

 

๐Ÿ“Œ TodoItem.js

const TextBox = styled.input`
  flex: 1;
  display: inline;
  border: none;
  outline: none;
  background-color: ${(props) => (!props.readOnly ? "rgb(0, 0, 0, 0.1)" : "transparent")};
  width: 500px;
  cursor: ${(props) => (!props.readOnly ? "defalut" : "pointer")};
  text-decoration: ${(props) => (props.checked ? "line-through" : "none")};
  color: ${(props) => (props.checked ? "gray" : "black")};
  font-size: 1rem;
`;
// props๋ฅผ ์ด์šฉํ•ด์„œ css๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์„ค์ • ํ•  ์ˆ˜ ์žˆ๋‹ค.

function TodoItem({todo}) {
  const {id, text, isCompleted} = todo;
  const [readOnly, setReadOnly] = useState(true);
  const [updateText, setUpdateText] = useState(text);
  const dispatch = useDispatch();

  const onChangeText = (e) => {
    const value = e.target.value;
    setUpdateText(value);
  }

  const updateTodo = () => {
    if(!isCompleted) {
      setReadOnly(!readOnly);
    }
  };

  return(
    <TodoItemBox>
      <CheckBox onClick={() => dispatch(todoToggle(id))}>
        {isCompleted ? (
          <FiCheckSquare size="25px" />
        ) : (
          <FiSquare size="25px" color="black" />
        )}
         {/* ์ฒดํฌ๋ฐ•์Šค ๋ˆ„๋ฅด๋ฉด todoToggle์„ ๋ถˆ๋Ÿฌ์™€์„œ isComplete ์†์„ฑ์„ ๋ฐ”๊ฟ”์ค€๋‹ค */}
      </CheckBox>
      <TextBox 
        name="text" 
        readOnly={readOnly}
        defaultValue={text} 
        checked={isCompleted} 
        onChange={onChangeText}
        onBlur={() => dispatch(todoUpdate(id, updateText))}
        />
      {/* ๋‚˜์ค‘์— ์ˆ˜์ •๊ธฐ๋Šฅ ์œ„ํ•ด์„œ readOnly ์„ ์–ธํ•ด์ค€๋‹ค. */}
      {/* onBlur => TextBox์— ํฌ์ปค์Šค๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉด ๋ณ€๊ฒฝ๋œ ํ…์ŠคํŠธ ๊ฐ’์„ dispatch๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์Šคํ† ์–ด์— ๋ฐ˜์˜ */}

      ...

      <Button onClick={() => dispatch(todoRemove(id))}>
      {/* ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด todoRemove์„ ๋ถˆ๋Ÿฌ์™€์„œ ๊ทธ id๋ฅผ ๊ฐ€์ง„ todo๋ฅผ ์‚ญ์ œํ•œ๋‹ค. */}
        <BsFillTrashFill size="25px" color="#e56b6f" />
      </Button>
    </TodoItemBox>
  )
}

 

๐Ÿ“Œ MemoItem.js (textarea ๋†’์ด ์„ค์ • -> ๋ฐ›์•„์˜จ text ๊ธธ์ด์— ๋”ฐ๋ผ textarea์˜ ๋†’์ด๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•ด์ค€๋‹ค)

const TextBox = styled.textarea`
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  outline: none;
  background-color: ${(props) => (!props.readOnly ? "rgb(0, 0, 0, 0.1)" : "transparent")};
  cursor: ${(props) => (!props.readOnly ? "defalut" : "pointer")};
  width: 100%;
  text-align: center;
  font-size: 1rem;
  height: ${(props) => props.line !== 1 ? `${props.line * 21}px` : "24px"};
  resize: none;
  font-weight: 200;
`;

function MemoItem({memo}) {
  ...
  return(
    <MemoItemBox>
      <div className="textContainer">
        <TextBox 
          name="text" 
          readOnly={readOnly}
          defaultValue={text} 
          checked={isCompleted} 
          onChange={onChangeText}
          onBlur={() => dispatch(memoUpdate(id, updateText))}
          line={Math.ceil(text.length / 23)}
        ></TextBox>
      </div>
      ...
    </MemoItemBox>
  )
}

โžฐ textarea์— line์ด๋ผ๋Š” props๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋Š”๋ฐ, ์ „์ฒด ํ…์ŠคํŠธ ๊ฐœ์ˆ˜๋ฅผ ๋Œ€๋žต ํ•œ ์ค„์— ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ํ…์ŠคํŠธ ๊ฐœ์ˆ˜๋กœ ๋‚˜๋ˆ ์ค€๋‹ค(๋ฐ›์•„์˜จ ํ…์ŠคํŠธ๊ฐ€ ๋ช‡ ์ค„์ธ์ง€ ๋Œ€๋žต ๊ณ„์‚ฐ ํ•˜๋Š” ๊ฒƒ)

โžฐ css๋ฅผ ์ง€์ •ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์—์„œ line์„ ๋ฐ›์•„์„œ, ๋Œ€๋žต ํ•œ์ค„์˜ ๋†’์ด์ธ 21์„ ๊ณฑํ•ด์„œ ํ…์ŠคํŠธ ๋†’์ด์— ๋”ฐ๋ผ textarea ๋†’์ด๋„ ๋‹ค๋ฅด๊ฒŒ ์ง€์ •ํ•ด์ค€๋‹ค.

โžฐ ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์ •ํ™•ํžˆ ๋”ฑ ๋งž์•„๋–จ์–ด์ง€๊ฒŒ ๊ณ„์‚ฐ ํ•  ์ˆ˜๊ฐ€ ์—†์–ด์„œ ๋นˆ ๊ณต๊ฐ„์ด ์ƒ๊ธฐ๊ฑฐ๋‚˜, ๊ธ€๊ผด์— ๋”ฐ๋ผ ์›ํ•˜๋Š” ๋†’์ด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์„ ์ˆ˜๋„ ์žˆ๋‹ค.

โžฐ ๊ทธ๋ž˜์„œ ๋งŽ์ด ๊ฒ€์ƒ‰ํ•ด๋ณด๊ณ  ์•Œ์•„๋ดค๋Š”๋ฐ, useRef๋ฅผ ์“ฐ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋Š”๋ฐ ๋‚ด ์ฝ”๋“œ์— ์ ์šฉํ•ด์„œ ์—ฌ๊ธฐ์ €๊ธฐ ์ˆ˜์ •ํ•ด๋ด๋„ ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์—ˆ๋‹ค..ใ… ใ… 


๐Ÿฐ. ์‹œ์—ฐ ํ™”๋ฉด

 

๋ฐฐํฌํ• ๋ ค๊ณ  ์ด๊ฒƒ์ €๊ฒƒ ๋งŒ์กŒ๋‹ค๊ฐ€ 

ํˆฌ๋‘๋ฆฌ์ŠคํŠธ ์›น์ด ์•ˆ์—ด๋ ค์„œ...ใ…Ž ใ…..

์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ฆฌ์•กํŠธ ํ”Œ์  ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์„œ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ถ™์—ฌ๋„ฃ๊ธฐ ํ•˜๋ฉด์„œ ์—๋Ÿฌ ์ฐพ์•„๋ดค๋Š”๋ฐ ์•„์ง๋„ ์–ด๋””๊ฐ€ ์—๋Ÿฐ์ง€ ๋ชจ๋ฅด๊ณ˜์Œใ… ใ… ใ… ใ… ใ… 

์›๋ž˜ ํ”Œ์ ์—์„œ Provider ์“ฐ๋ฉด hooks invalid ์—๋Ÿฌ๊ฐ€ ๋œจ๋˜๋ฐ ์ƒˆ๋กœ ๋งŒ๋“  ํ”Œ์ ์—์„œ๋Š” ๊ทธ๋Œ€๋กœ ํ•ด๋„ ์•ˆ๋œจ๊ณ ..

์ง„์งœ ์งœ์ฆ๋‚œ๋‹ค ์ •๋ง ใ…Žใ…Žใ…Ž

์–ด์จŒ๋“  ๋ณต๊ตฌ ์™„..

์ด์ œ ์•„๋ฌด๊ฒƒ๋„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์„ํ…Œ์•ผ ๋ฐฐํฌ ๋‚˜์ค‘์— ํ• ๋ž˜..