본문 바로가기
Web Study/한입크기로잘라먹는리액트

한입 크기로 잘라먹는 리액트 - 4

by 쿠리의일상 2023. 3. 30.

전에 더미 데이터를 선언해주고 그 데이터만 일기 리스트에 보이게 만들었었다.

이번에는 이 더미 데이터를 없애주고, 직접 일기를 입력해준 내용이 나오게 만들어줘본다.

 

부모 컴포넌트인 App 에서 data를 관리하는 state 함수를 만들어주고 일기를 입력 받는 자식 컴포넌트에게는 해당 입력 필드의 값을 읽어서 데이터를 추가해줄 수 있는 setData가 포함된 함수를 넣어주고

일기를 보여주는 자식 컴포넌트에는 입력 받은 data 자체를 보내줘서 관리해준다.

function App() {
  const [data,setData] = useState([]);
  const dataId = useRef(0);
  const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      id : dataId.current,
      author,
      content,
      emotion,
      created_date,
    }
    dataId.current += 1;

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

  return (
    <div>
      <DiaryEditor onCreate={onCreate} />
      <DiaryList diaryList={data} />
    </div>
  );
}

 

삭제하기

이미 관리되고 있는 data 는 배열 형태므로 삭제하려는 item에 삭제 버튼을 만들어서,

id 정보를 가져와서 배열 filter() 메서드를 사용하여 준다.

다만, App -> DiaryList -> DiaryItem 까지 두 단계를 거쳐서 Props 를 넘겨줘야 한다.

// App.js
const onDelete = (id) => {
    const newData = data.filter((e, idx) => idx !== id);
    setData(newData);
}

 

export default function DiaryItem({ author, content, emotion, created_date, id, onDelete}) {
  return (
    <div className='DiaryItem'>
      <div className='info' key={id}>
        <span>작성자 : {author} | 감정 점수 : {emotion}</span>
        <br/>
        <span className='date'>{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className='content'>{content}</div>
      <button onClick={() => {
        if(window.confirm(`${id}번 일기를 정말 삭제하시겠습니까?`))
          onDelete(id);
      }}>삭제하기</button>
    </div>
  )
}

window.confirm() 함수는 예/아니오 창을 띄우고 boolean 값을 리턴해준다

 

삭제는 id 값만 읽어와서 원래 data 에서 삭제해주면 되므로 어렵지 않았으나...

 

글 수정하기

조금 복잡하다...

  const onEdit = (id, newContent) => {
    setData(
      data.map((e) => e.id === id ? 
      {...e, content: newContent} 
      : e)
    );
  }

App.js 에 만들어준 수정 함수로 setData 를 사용하여 DiaryItem 에서 수정된 값을 newContent 로 가져와서

변경해주는 id와 동일한 경우 원래 data 내용 뒤에 { ...e, content: newContent } 로 바뀐 내용을 넣어준다.

 

DiaryItem 에선 글 수정을 위하여 2개의 State 를 지정해주는데,

수정 모드인지 아닌지 검사해주는 boolean state 와

수정 전후 값에 대한 state 인 localContent

 

전통적으로 수정 모드인지 아닌지 검사해주는 state 인 isEdit은 false 로 두었다가, 변화가 필요할 때

  const [isEdit, setIsEdit] = useState(false);
  const toggleIsEdit = () => setIsEdit(!isEdit);

!isEdit 을 setIsEdit() 으로 수정해서 토글 기능을 수행하게 만든다.

또한 수정하기 버튼을 누르면 -> 원래 글의 컨텐츠 부분이 인풋 필드로 변경되면서 글의 원래 내용도 나와있어야 함

-> 버튼이 수정취소와 수정완료 로 바뀌어야 한다.

 

<div className='content'>
        {isEdit ? 
          <textarea 
            value={localContent} 
            onChange={(e) => setLocalContent(e.target.value)}
            ref={localContentInput}
            /> 
          : 
          <>{content}</>
          }
      </div>
      {isEdit ? 
        <>
          <button onClick={handleQuitEdit}>수정취소</button>
          <button onClick={handleEdit}>수정완료</button>
        </>
        : <>
          <button onClick={handleRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      }

수정 모드 state 인 isEdit 과 3항 연산자로 응용

수정 모드일 때 textarea 의 value 는 새로 입력 받는 localContent 가 되어야 하며, 이는 setLocalContent() 로

입력되는 값에 따라 (e.target.value) 변경된다.

 

수정 모드에서 수정취소의 경우, setIsEdit() 을 false, setLocalContent()는 원래 content 값으로 해주면 된다.

수정 완료의 경우에는 App.js 에서 가져온 onEdit() 함수와 더불어 toggleIsEdit() 을 재실행 해줘서 리렌더링해줘야 한다.

 

export default function DiaryItem({ 
  author, content, emotion, created_date, id, onRemove, onEdit
}) {
  const [isEdit, setIsEdit] = useState(false);
  const toggleIsEdit = () => setIsEdit(!isEdit);

  const [localContent, setLocalContent] = useState(content);

  const localContentInput = useRef();
  
  const handleRemove = () => {
    if(window.confirm(`${id}번 일기를 정말 삭제하시겠습니까?`))
      onRemove(id);
  }

  const handleQuitEdit = () => {
    setIsEdit(false);
    setLocalContent(content);
  }

  const handleEdit = () => {
    if(localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }

    if(window.confirm(`${id}번 일기를 수정하시겠습니까?`)) {
      onEdit(id, localContent);
      toggleIsEdit();
    }
  }

  return (
    <div className='DiaryItem'>
      <div className='info' key={id}>
        <span>작성자 : {author} | 감정 점수 : {emotion}</span>
        <br/>
        <span className='date'>{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className='content'>
        {isEdit ? 
          <textarea 
            value={localContent} 
            onChange={(e) => setLocalContent(e.target.value)}
            ref={localContentInput}
            /> 
          : 
          <>{content}</>
          }
      </div>
      {isEdit ? 
        <>
          <button onClick={handleQuitEdit}>수정취소</button>
          <button onClick={handleEdit}>수정완료</button>
        </>
        : <>
          <button onClick={handleRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      }
    </div>
  )
}

 

 

Lifecycle Methods

생성 Mount : 화면에 나타나는 것

  • componentDidMount

 

변화 Update : 리렌더 하는 것 -> state, props 변경

  • componentDidUpdate

 

제거 UnMount : 화면에서 사라짐

  • componentWillUnmount

---- 위의 생명주기 메서드는 클래스형 컴포넌트에서만 사용 가능

 

 

함수형 컴포넌트에서는?

useEffect 라는 훅을 사용하여 가능하다!

useEffect(콜백함수, deps-의존성 배열)

useEffect(() => {

}, []);

 

// Mount 되는 순간만 콜백함수 실행
useEffect(() => {
	console.log('Mount');
}, []);
// 컴포넌트가 리렌더링 될 때마다 실행
useEffect(() => {
    console.log('Update');
});
  // 특정 상태가 리렌더링 될 때마다 실행
  useEffect(() => {
    console.log(`count is update : ${count}`);
  }, [count]);
  useEffect(() => {
    console.log(`text is update : ${text}`);
  }, [text]);
// 컴포넌트가 사라질 때 실행 - useEffect() 안에 return 으로 함수를 리턴해주면 된다.
useEffect(() => {
	return () => {
    	console.log('Unmount');
    }
}, []);

 

 

const UnmountText = () => {
  
  useEffect(() => {
    console.log('Mount');
    return () => {
      console.log('Unmount');
    }
  }, []);

  return (
    <div>Unmount Testing Component</div>
  )
}

export default function Lifecycle() {
  const [isVisible, setIsVisible] = useState(false);
  const toggle = () => setIsVisible(!isVisible);
  return (
    <div style={{padding: '20px'}}>
      <button onClick={toggle}>On/Off</button>
      {isVisible && <UnmountText/>}
    </div>
  )
}

해당 코드는 토글 버튼을 눌러서, 컴포넌트를 보였다가 사라졌다가 해주는 예제인데,

이때 isVisible 이 true 인 상태에만 보이게끔 하는 방식은 { isVisible && <컴포넌트명 /> } 형태이다.

단락 회로 평가? 에 의하여 false 인 경우는 뒤의 조건은 보지도 않기 때문에 토글 기능을 구현해줄 수 있다.

 

위의 방식으로 UnmountText 컴포넌트를 조절하여, useEffect() 로 Mount 와 Unmount 를 출력해볼 수 있다.