본문 바로가기

JS/React Project

🏃‍♀️ 목표 다이어리 - 글 작성 / 수정

필요한 기능 

1. 날짜, 주제 선택 

2. 목표 추가 버튼 누르면 목표가 리스트로 화면에 출력 + 수정 가능 

3. 일기 작성 

4. 모두 선택 및 작성 완료 후 작성완료 버튼을 누르면 데이터에 추가되며 홈화면으로 돌아감. 

4. 작성 취소를 누르면 작성 내용 모두 리셋되고 홈화면으로 돌아감 

 

 

다이어리에는 글 새로 작성과 수정이 있는데, 모두 똑같은 form을 가지고 있고 수정의 경우 초기 데이터값과 전달되는 함수만 다른 것이다. 

그러니 Editor 컴포넌트로 분리하여 두 페이지에서 모두 사용할 수 있다. 

 

 

1. 날짜 선택 

// 날짜 형식 변환 
const getStringDate = (date) => { 
    return date.toISOString().slice(0, 10)
}

const DiaryEditor =() => {

    // 입력되는 date를 관리할 state (초기값은 오늘 날짜) 
    const [date, setDate] = useState(getStringDate(new Date)); 
    
    // 이때, 초기값에 그냥 new Date로만 하면 형식이 맞지 않아 초기값 설정이 되지 않는다. 
    // console.log(new Date)  : Mon Aug 07 2023 03:33:14 GMT+0900 (한국 표준시)
    
    // 날짜의 형식을 변환하는 함수를 만들어 이용해야 한다.
    // console.log(getStringDate(new Date)) : 2023-08-06

     return 
      <span>날짜 : </span>
      {/* value의 초기값은 date의 값, onChange로 setDate */}
       <input value = {date} 
              onChange={(e)=> setDate(e.target.value)}
              type="date" />


}

 

이때 날짜를 변환하는 함수는 앱 여기저기서 쓰일 수 있기 때문에 따로 함수를 분리하여 사용하면 편리하다. 

// date.js 
export const getStringDate = (date) => {
    return date.toISOString().slice(0, 10)
}

// DiaryEditor.js에서 import하여 사용 
import {getStringDate} from "../util/date"

 

 

2. 주제 선택 

주제를 선택하였을 때 어떤 주제가 선택되었는지 위해 subjectList와 SubjectItem을 각각 컴포넌트로 분리하여 사용한다. 

// SubjectList.js
export const subjectList = [
    {
        subject_id : 1, 
        subject_img : process.env.PUBLIC_URL + `/assets/daily.png`,
        subject_name : "일상"
       },
       {
        subject_id : 2, 
        subject_img : process.env.PUBLIC_URL + `/assets/study.png`,
        subject_name : "공부"
          },
       {
        subject_id : 3, 
        subject_img : process.env.PUBLIC_URL + `/assets/workout.png`,
        subject_name : "운동"
       },
       {
        subject_id : 4, 
        subject_img : process.env.PUBLIC_URL + `/assets/saving.png`,
        subject_name : "저축"
       },
]

// SubjectItem.js
const SubjectItem = ({subject_id, subject_img, subject_name, onClick}) => { 

    return (
        // 클릭되었을 때 전달할 props을 onClick으로 설정하고, 
        // 해당 아이템이 클릭되었을 때(onClick =) onClick prop을 이용하여 id를 전달한다. 
        <div className="editor_subject_item" onClick={()=> onClick(subject_id)}>
            <img src={`${subject_img}`} />
            <span>{subject_name}</span>
        </div>
    )

}

export default SubjectItem;

 

DiaryEditor에서 위의 컴포넌트를 사용할 수 있다. 

const DiaryEditor =() => {
	
    // 작성된 주제를 관리할 State (초기값 : 1(일상))
     const [subject, setSubject] = useState(1);
     
     return 
     <div className="editor_subject_wrapper">
        <span>주제 : </span>
        {subjectList.map((it)=> 
                        <SubjectItem key={it.subject_id} {...it} 
                                     onClick={handleSubject}
        )}
    </div>
}

여기까지하면 개발자툴에서 클릭된 주제가 state에 저장된 것을 알 수 있다. 

 

그럼 ui 면에서 어떤 주제가 선택되었는지 확인하여야 하는데

// DiaryEditor.js
<div className="editor_subject_wrapper">
    <span>주제 : </span>
    {subjectList.map((it)=> 
                    <SubjectItem key={it.subject_id} {...it} 
                                 onClick={handleSubject}
                                 isSelected={it.subject_id === subject}/>
    )}
</div>

// isSelected를 해당 item의 subject_id가 subject(id)와 같다면 true, 다르면 false를 반환한다. 
// ex) 현재 2번을 선택했다면, 1, 3, 4번의 item은 false, 2번은 true를 반환

DiaryItem에서 이 true, fasle 값을 이용하여 선택되었을 때 className을 따로 주어 css를 설정하면 된다. 

const SubjectItem = ({subject_id, subject_img, subject_name, onClick, isSelected}) => { 

    return (
        <div className={["editor_subject_item", `editor_subject_item_${isSelected}`].join(" ")} 
             onClick={()=> onClick(subject_id)}> 
        // ... 
	)
}

 

 

3. 목표 추가 

+ 버튼을 누르면  <li><input /><li> 가 추가되고 이를 삭제할 수도 있다. 

const DiaryEditor =() => {

    // 목표의 상태를 관리할 state (배열의 형태로 저장될 것이다.) 
    const [goal, setGoal] = useState([]);
    
     // 목표 추가 
     // goal 배열을 복사한 뒤, 새로운 빈 배열을 추가하여 setGoal로 상태를 저장한다. 
     const addGoal = () => { 
        const goals = [...goal, []]
        setGoal(goals)
    }

    // 목표 삭제 
    // goal 배열을 복사한 뒤, 해당 인덱스에 위치한 요소를 splice 메소드를 사용하여 삭제한 후  setGoal로 상태를 저장한다.
    const deleteGoal = (index) => { 
        const deleteTarget = [...goal];
        deleteTarget.splice(index,1);
        setGoal(deleteTarget);
    }

    // 목표 입력 관리 
    // goal 배열을 복사한 뒤, 해당 인덱스에 위치한 요소를 사용자가 입력한 값으로 업데이트한 후, setGoal로 상태를 저장한다.
    const handleGaol = (e, index) => { 
        const inputGaol = [...goal];
        inputGaol[index] = e.target.value;
        setGoal(inputGaol);
    }
    
    
    // goal 배열을 map으로 순회하며 목표를 <li> 형태로 표시한다.
    // 각 리스트마다 <input>이 있고 이 <input>의 값이 변경될 때 handleGoal함수를 호출하고,  
    // 삭제 버튼(x)을 클릭할 때 deleteGoal함수를 호출한다. 
    return 
        <div className="editor_goals_area">
            <ul className="goal_list">
                {goal.map((goal, index)=> {
                    return ( <li key={index}>
                        <input value={goal} 
                               onChange={(e) => handleGaol(e, index)}/>
                        <MyButton text={"X"} onClick={()=>deleteGoal(index)}/>
                    </li> )
                })}
            </ul>
        </div>

}

 

 

 

모두 작성하고 작성완료 버튼을 누르면 onCreate 함수를 불러와 데이터를 전달하면 된다. 

 

 

 

 


수정하기 

새 글 작성과 form은 같지만 선택한 다이어리의 원래 데이터가 그 form 안에 있어야 한다. 

 

edit 페이지 컴포넌트에서 다음과 같이 작성한다. 

// EditDiary.js
const EditDiary = () => {

    // 선택된 다이어리의 id 가져오기 
    const { id } = useParams();

    // 사용할 data 가져오기 
    const diaryList = useContext(DiaryStateContext);

    // Editor form에 들어갈 데이터 state
    const [originData, setOriginData] = useState();

    const navigate = useNavigate();

    // 마운트되었을 때 id와 일치하는 데이터 가져오기 
    useEffect(()=> {

        // diaryList에 데이터가 있을 경우, 데이터 중 현재 페이지의 id와 같은 다이어리를 배열에 담음 
        if(diaryList.length >=1) { 
            const targetDiary = diaryList.find((it)=> parseInt(it.id) === parseInt(id))
        
            // 만약 targetDiary가 있다면 originData를 그것으로 설정하고 없다면 홈화면으로 돌아감
            if(targetDiary) { 
                setOriginData(targetDiary);
            } else { 
                navigate('/', {replace:true})
            }
        }

    },[diaryList, id])

    // originData가 있다면 DiaryEditor 컴포넌트를 렌더링 함.
    // isEdit: diaryEditor가 새 글 작성인지 글 수정인지 확인.
    // origindata: 현재 id의 데이터 
    return <div> 
        {originData && <DiaryEditor isEdit={true} originData={originData}/>}
    </div>
}

 

prop으로 전달한 isEdit과 originData를 DiaryEditor에서 전달받아 사용한다.

const DiaryEditor =({isEdit, originData}) => {

}