반응형

저장없이 배열에 내용을 추가, 읽기, 수정, 삭제하는 내용입니다.

버튼을 조각조각 내서 재사용성을 늘렸습니다.

 

내부 트리

├─ src
│  ├─ App.css
│  ├─ App.tsx
│  ├─ ProtectRouter.tsx
│  ├─ components
│  │  ├─ crud
│  │  │  └─ CRUD.tsx
│  │  ├─ item
│  │  │  ├─ CreateButton.tsx
│  │  │  ├─ TextInput.tsx
│  │  │  ├─ Textarea.tsx
│  │  │  └─ UpdateButton.tsx
│  │  └─ ts
│  │     └─ interface.ts
│  ├─ index.tsx
│  ├─ react-app-env.d.ts
│  └─ styles
│     └─ CRUD.module.scss
└─ tsconfig.json

 

ts

./ts/interface.ts

import { Dispatch, SetStateAction } from "react";

export interface TextProps {
    text: string,
    setText: Dispatch<SetStateAction<string>>
}

export interface ButtonProps {
    btn: boolean,
    name: string,
    setBtn: Dispatch<SetStateAction<boolean>>
}

export interface ArrayProps {
    id: number,
    title: string,
    contents: string,
}

 

item

./item/CreateButton.tsx

import React from "react";
import { ButtonProps } from "../ts/interface";

export const CreateButton: React.FunctionComponent<ButtonProps> = ({ name, btn, setBtn}) => {

    const onClick = () => {
        setBtn(true);
    }

    return (
        <>
            <button onClick={onClick}>{name}</button>
        </>
    )
};

 

./item/Textarea.tsx

import React from "react";
import { TextProps } from "../ts/interface";

export const Textarea: React.FunctionComponent<TextProps> = ({ text, setText }) => {

    const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const { target: { value } } = e;

        setText(value);
    }

    return (
        <>
            <textarea value={text} onChange={onChange} placeholder="내용을 입력해주세요." />
        </>
    );
};

 

./item/TextInput.tsx

import React from "react";
import { TextProps } from "../ts/interface";

export const TextInput: React.FunctionComponent<TextProps> = ({ text, setText }) => {

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { target: { value } } = e;

        setText(value);
    }

    return (
        <>
            <input type="text" value={text} onChange={onChange} placeholder="제목을 입력해주세요." />
        </>
    );
};

 

./item/UpdateButton.tsx

import React from "react";
import { ButtonProps } from "../ts/interface";

export const UpdateButton: React.FunctionComponent<ButtonProps> = ({ name, btn, setBtn}) => {

    const onClick = () => {
        setBtn(true);
    }

    return (
        <>
            <button onClick={onClick}>{name}</button>
        </>
    )
};

 

crud

./crud/CRUD.tsx

import { useCallback, useEffect, useState } from "react";
import styles from "../../styles/CRUD.module.scss";
import { CreateButton } from "../item/CreateButton";
import { Textarea } from "../item/Textarea";
import { TextInput } from "../item/TextInput";
import { UpdateButton } from "../item/UpdateButton";
import { ArrayProps } from "../ts/interface";

export const CRUD = () => {
    const [title, setTitle] = useState<string>("");
    const [contents, setContents] = useState<string>("");
    // false: createBtn | true: updateBtn
    const [btnChange, setBtnChange] = useState<boolean>(false);
    const [createBtn, setCreateBtn] = useState<boolean>(false);
    const [updateBtn, setUpdateBtn] = useState<boolean>(false);
    const [array, setArray] = useState<ArrayProps[]>([]);
    const [changeCount, setChangeCount] = useState<number>(0);
    // 계속 증가하는 id Number
    const [count, setCount] = useState<number>(0);
    
    // 글 생성
    const list = useCallback(() => {
        setArray(prev => [...prev, {id: count, title: title, contents: contents}]);
        setCount(prev => prev + 1);
        setTitle("");
        setContents("");
        setCreateBtn(false);
    }, [title, contents, count]);
    useEffect(() => {
        if (createBtn) list();
    }, [createBtn, list]);

    // 글 업데이트
    useEffect(() => {
        if (updateBtn) {
            let update = [...array];

            update[changeCount].title = title;
            update[changeCount].contents = contents;

            setArray(update);
            setTitle("");
            setContents("");
            setBtnChange(false);
            setUpdateBtn(false);
        }
    }, [updateBtn, array, title, contents, changeCount])

    // 삭제 | 업데이트
    const onUpdate = (list: string, item: ArrayProps) => {

        if (list === "delete") setArray(array.filter(arr => arr.id !== item.id));
        else if (list === "update") {
            setChangeCount(item.id);
            setTitle(item.title);
            setContents(item.contents);
            setBtnChange(true);
        }
    }

    return (
        <article className={styles.crud_wrap}>
            <div className={styles.text_wrap}>
                <TextInput text={title} setText={setTitle} />
            </div>
            <div className={styles.contents_wrap}>
                <Textarea text={contents} setText={setContents} />
            </div>
            <div className={styles.button_wrap}>
                {
                    btnChange ? <UpdateButton name="업데이트" btn={updateBtn} setBtn={setUpdateBtn} /> : <CreateButton name="확인" btn={createBtn} setBtn={setCreateBtn} />
                }
            </div>
            <ul>
                {
                    array.length > 0 && array.map(item => (
                        <li key={item.id}>
                            <h2>{item.title}
                                <div className={styles.arr_wrap}>
                                    <span onClick={() => onUpdate("update", item)}>업데이트 | </span>
                                    <span onClick={() => onUpdate("delete", item)}>삭제</span>
                                </div>
                            </h2>
                            <p>{item.contents}</p>
                        </li>
                    ))
                }
            </ul>
        </article>
    );
}

 

App.tsx

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

// Components
import { CRUD } from './components/crud/CRUD';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/crud" element={<CRUD />} />
      </Routes>
    </Router>
  );
}

export default App;

 

styles

./styles/CRUD.module.scss

* {
    box-sizing: border-box;
}
.crud_wrap {
    width: 100%;
    max-width: 500px;
    margin: 0 auto;
    border: 2px solid #DDD;
    border-radius: 16px;
    padding: 2rem;
    box-sizing: border-box;
    > div {
        margin-bottom: 1rem;
    }
    .text_wrap {
        > input { width: 100%; padding: .5rem; border: 1px solid #DDD; border-radius: 16px; }
    }
    .contents_wrap {
        > textarea { width: 100%; padding: .5rem; border: 1px solid #DDD; border-radius: 16px; resize: none; }
    }
    .button_wrap {
        margin-top: 2rem;
        > button { width: 100%; padding: .5rem; background: #DDD; border-radius: 16px; border: 0; color: #FFF; font-size: 16px; font-weight: 700; }
    }
    > ul {
        list-style: none;
        padding: 0;
        > li {
            > h2 {
                position: relative;
                font-size: 1.2rem;
                .arr_wrap {
                    position: absolute;
                    top: 0;
                    right: 0;
                    > span {
                        font-size: .8rem;
                        font-weight: 500;
                        cursor: pointer;
                    }
                }
            }
            > p { font-size: 1rem; }
        }
    }
}
반응형

+ Recent posts