본문 바로가기

JS/React Project

📝 간단한 메모앱 만들기

컴포넌트

  • MemoEditor: 메모를 입력
  • MemoBoard: 메모가 나타날 공간
  • MemoItem: 각각의 메모 

 

1. 메모 작성하기 

 

MemoEditor.js

- State를 사용하여 입력된 값들을 저장 

const [state, setState] = useState({
    subject : "",
    title : "",
    content : "",
});

 

- 내용 입력 시 setState할 함수 생성

const handleChangeState = (e) => {
        setState({
            ...state,
            [e.target.name] : e.target.value
        });
    }
  1. 각각의 입력폼에 onChange={handleChangeState} 를 입력하여 해당 요소가 변화할 때 호출 될 함수를 만들었다. 
  2. event를 매개변수로 받고 스프레드 연산자(...)을 사용하여 이전의 내용을 복사한다. 
  3. [e.target.name] : e.target.value =  이벤트가 발생한 요소의 name을 [key] 로, 값을 value로 저장한다. 

 

💡 useRef를 사용하여 focus 이동하기 

  * useRef ? 

    DOM 요소에 대한 참조를 생성하고 유지하기 위해 사용되는 Hook으로 함수형 컴포넌트에서도 DOM요소에 직접 접근할 수 있다. 

// focus할 대상과 함수 
const titleInput = useRef();
const contentInput = useRef();

// focus 실행 
if(state.title.length == 0) {
  titleInput.current.focus();
  return
}

// focus 요소
<input ref={titleInput} />

 

 

이렇게 작성된 메모는 MemoList로 전달이 되어야 하는데

이와 같이 MemoEditor와 MemoList는 같은 레벨의 컴포넌트로 데이터를 주고받을 수 없다. 

만약 데이터를 여러 컴포넌트에서 사용해야 한다면, 공통부모인 App.js에서 State를 관리하고 하위 컴포넌트로 데이터를 전달해야 한다.

  1. App.js에서 State를 정의하고 업데이트 하는 메소드 작성 
  2. 하위 컴포넌트에 필요한 데이터를 props로 전달 
  3. 하위 컴포넌트는 props를 통해 전달받은 데이터를 사용하고, 필요한 경우 eventHandler를 통해 App.js로 데이터를 전달한다. 

 

App.js

// 전역적으로 관리할 data State
const [data, setData] = useState([]);

// memo ID
const dataId = useRef();
// useRef를 사용하는 이유?
//  - DOM 요소에 직접 접근할 수 있기 때문에 이를 통해 DOM 요소의 속성이나 메서드에 접근하거나 
//    직접 조작할 수 있다. 이는 컴포넌트 간에 데이터를 공유하는 데 사용되며 
//    .current를 사용하여 값을 읽거나 업데이트 할 수 있다.


/**
* MemoEditor에서 메모의 내용을 받아와 data에 set하는 함수 
* @param {*} subject 
* @param {*} title 
* @param {*} content 
*/
const onCreate = (subject, title, content) => {

// 작성일 
const writtenDate = new Date().getTime();

const newItem = {
  subject, 
  title, 
  content,
  writtenDate,
  id: dataId.current,
}

dataId.current += 1;

setData([newItem, ...data]);

}

// ... 


// MemoEditor에서 onCreate 함수를 사용할 수 있도록 prop으로 전달
<MemoEditor onCreate={onCreate} />

// data를 Memboard에서 사용할 수 있도록 prop으로 전달 
<MemoBoard memoBoard={data} />

 

MemoEdiotr.js

// onCreate 함수를 App.js에서 받아옴
const MemoEditor = ({onCreate}) => { 

//.. 중략

    // '작성'버튼을 누르면 실행될 함수에 onCreate를 사용하여 매개변수를 통해 App.js로 데이터 전달
     const handleSubmit = () => {
        onCreate(state.subject, state.title, state.content);
     }
 
 }

 

MemoBoard.js

// App.js에서 prop로 전달한 data를 받음 (memoBoard = {data} -> memoBoard는 prop이름)
const MemoBoard = ({memoBoard}) => {
	{memoBoard.map((it)=>(
           <MemoItem key={it.id} {...it}/>
     ))}
}
// map함수를 통해 순회하며 각 it은 memoBoard 배열의 원소 객체를 나타내며, 
// 각각의 Item에 key로 id를 지정하고, {...it}를 통해 객체의 모든 속성들을 개별적인 props로 전달한다. 
// 이로써 MemoItem 컴포넌트는 memoBoard의 각 원소에 대한 정보를 개별적으로 받아와 사용할 수 있게 된다.

 

MemoItem.js

const MemoItem = ({id, subject, title, content, writtenDate}) => {}
// memoBoard에서 전달한 개별 속성들을 매개변수로 받아와서 사용하면 된다.

 

 

2. 메모 삭제 및 수정 

 

(삭제) 

삭제 버튼을 클릭하면 App.js의 State가 변해야 하기 때문에 App.js에서 삭제를 하는 함수를 만들어서 하위 컴포넌트로 전달해야 한다. 

ex) data: [{item1}, {item2}, {item3}]  -- (item2삭제 시) --> [{item1}, {item3}]

 

App.js 

const onDelete = (targetId) => {
    // fitler 함수를 사용하여 data 중에서 id가 targetId와 같지 않은 요소들로 이루어진 새로운 배열 생성
    const newMemoBoard = data.filter((it)=>it.id !== targetId);
    setData(newMemoBoard);
}

이렇게 만든 onDelete 함수를 하위 컴포넌트로 전달한다. 

// App.js
<MemoBoard memoBoard={data} onDelete={onDelete}/>

// MemoBoard.js  * 이 컴포넌트에서는 사용되지 않아도 MemoItem으로 전달하기 위해 추가해야함) 
const MemoBoard = ({onDelete, memoBoard}) => {
	<MemoItem key={it.id} {...it} onDelete={onDelete}/>
}

// MemoItem.js 
const MemoItem = ({onDelete, id, subject, title, content, writtenDate}) => {
	const handleDelete = () => {
    	    if (window.confirm(`${id}번째 메모를 삭제하시겠습니까?`)) onDelete(id);
 	   }
 
 	return <div>
    //  ...중략
     <button onClick={handleDelete}>&times;</button>
    </div>
 
 }

 

(수정)

삭제와 마찬가지로 수정이 완료되면 data의 값 또한 변화해야 하기 때문에 App.js에서 상태를 관리하며 이를 하위 컴포넌트로 전달한다. 

 

App.js 

const onEdit = (targetId, newContent) => {

    // id가 tragetId와 일치한다면 해당 요소는 수정 대상으로 content만 newContent로 변경, 
    // 일치하지 않으면 원래 요소 반환
    setData(
      data.map((it) => it.id === targetId ? {...it, content:newContent} : it)
    );

}

하위 컴포넌트로 전달 

// App.js
<MemoBoard memoBoard={data} onDelete={onDelete} onEdit={onEdit}/>

// MemoBoard.js  * 이 컴포넌트에서는 사용되지 않아도 MemoItem으로 전달하기 위해 추가해야함) 
const MemoBoard = ({onEdit, onDelete, memoBoard}) => {
	<MemoItem key={it.id} {...it} onDelete={onDelete} onEdit={onEdit}/>
}

 

MemoItem.js

const MemoItem = ({onEdit, onDelete, id, subject, title, content, writtenDate}) => {
	
    // 현재 내용이 수정중인지 아닌지 값을 보관할 State
    const [isEdit, setIsEdit] = useState(false);

    // 호출되면 기존 isEdit이 가지고 있던 값을 반대로 set (버튼 클릭 시 호출)
    const toggleIsEdit = () => setIsEdit(!isEdit);

    // 수정된 내용의 State (기초값으로 원래의 content 설정)
    const [editedContent, setEditedContent] = useState(content);

    // 수정 취소 버튼을 누르면 수정중이던 내용이 초기화되는 함수 
    const handleCancledEdit = () => {
        setIsEdit(false);
        setEditedContent(content);
    }

    // 수정 완료 시 App.js로 data를 보내는 event
    const handleEdit = () =>{
        if(window.confirm(`'${title}' 메모를 수정하시겠습니까?`)) {
            onEdit(id, editedContent);
            toggleIsEdit();
        } 
    }
    
    
    return <div>
    
    	// .. 중략
        
        // 메모 내용
        <div>
            {/* isEdit이 true일 경우 수정 form이 나옴 */}
            {isEdit ? 
                <>
                    <textarea 
                        className="editContent"
                        value={editedContent}
                        onChange={(e) => setEditedContent(e.target.value)}
                    />
                </>
                 : 
                <>{content}</>}
        </div>

        // 버튼 
        <div>
            {/* isEdit이 ture일 경우 수정취소/수정완료 버튼 */}
            {isEdit ? 
                <>
                    <button onClick={handleCancledEdit}>수정 취소</button>
                    <button onClick={handleEdit}>수정 완료</button>
                </>
                :
                <>
                    <button onClick={toggleIsEdit}>수정</button>    
                </>
            }
        </div>
    </div>
}

 

 

 

💡 메모 주제별로 배경색 지정하기 

 

MemoItem.js

// 메모 배경 색 설정
const memoBackColor = () => {
    if(subject === "기타") return "#ffff86"
    if(subject === "쇼핑") return  "skyblue"
    if(subject === "할 일") return "pink"
    if(subject === "공부") return "rgb(207, 238, 160)"
}


return <div className="memoItem" style={{ backgroundColor: memoBackColor() }}> ... </div>

 

처음에는 App.js에 만들어둔 onCreate 함수에서 위의 if문을 썼었는데 그러면 가장 최근에 추가된 메모는 색이 지정이 안되었다. 

색이 지정되는 타이밍이 문제라 생각하여 MemoItem이 생성될 때 <div class="memoItem"> .. </div> 여기에 인라인으로 스타일을 주어 추가되게 하는 게 좋을 거 같아 위처럼 작성하였더니 해결되었다....

 

 

 

완성!