hero 리팩토링
수정페이지는 등록페이지와 보기페이지를 합한 역할이다. 최초에 hero 정보를 가져온 후에 등록페이지 UI에 바인딩하면된다. 기존 Hero.js 파일은 Detail View 역할이므로 Hero.js 파일을 View.js로 리네임 리팩토링한다.
그리고, Hero.js 컴포넌트를 다시 만든다. 이 Hero.js가 하는 역할은 view와 edit 컴포넌트를 모드에 따라서 보여주는 역할을 한다. is_edit 변수가 false 이면 detail view 컴포넌트를 true이면 edit 컴포넌트를 보여준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import React, {useState} from 'react'; import {View} from "./View"; export const Hero = (props) => { console.log('View: ', props); const [is_edit, setIs_edit] = useState(false); const handleEditMode = (e) => { setIs_edit(!is_edit); } return ( <> <div className="d-flex justify-content-between align-items-center m-3"> <h3>{ is_edit ? 'Hero Edit' : 'Hero Detail View' }</h3> <div> { is_edit ? <button className="btn btn-info" onClick={handleEditMode}>취소</button> : <button className="btn btn-success" onClick={handleEditMode}>수정</button> } <button className="btn btn-danger ml-3">삭제</button> </div> </div> { is_edit ? <Edit id={props.match.params['id']}/> : <View id={props.match.params['id']} /> } </> ); } |
View 컴포넌트에는 id 속성으로 넘겨준다. 이제 View 컴포넌트는 uri 파라메터를 변수로 받을 수 없으므로 부모인 Hero에서 넘겨줘야 한다. View에서는 id로 받는 부분을 수정한다.
1 2 3 4 5 |
... useEffect(() => { getHero(props.id); }, [props.id]); ... |
Edit 컴포넌트 추가
수정하는 컴포넌트인 Edit 컴포넌트를 추가한다. Edit 컴포넌트는 데이터를 가져오는 Detail View 와 등록하는 컴포넌트인 Register 컴포넌트가 결합된 형태이다. 그러므로 먼저 Register 컴포넌트를 복사해서 pages/hero 폴더에 Edit.js로 복사한다. 그리고, Detail View 컴포넌트에서 데이터를 가져오는 부분을 추가한다. 데이터를 가져오게 되면 sex, power 부분을 수정해야 한다. sex에는 male or female 의 스트링 타입 데이터가 들어가 있는데 이것을 객체로 전환해야 하고 마찬가지로 power에도 콤마로 분리된 스트링 데이터를 객체로 전환해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
import React, {useEffect, useState} from 'react'; import api from "../../utils/api"; export const Edit = (props) => { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [sex, setSex] = useState({ male: true, female: false }); const [country, setCountry] = useState(''); const [address, setAddress] = useState(''); const [powers, setPowers] = useState({ flying: false, penetration: false, hacking: false, strength: false }); const [photo, setPhoto] = useState(''); useEffect(() => { getHero(props.id) }, [props.id]) const getHero = async (id) => { let response = await api.get(`/api/user/hero/${id}`); console.log(response); const hero = response.data; setName(hero.name); setEmail(hero.email); // hero = { ..., sex: 'female' } const sex = { male: false, female: false }; sex[hero.sex] = true; setSex(sex); // hero = { ..., powers: ['flying', 'strength'] } const powers = { flying: false, penetration: false, hacking: false, strength: false }; hero.powers.forEach(power => { powers[power.name] = true; }); setPowers(powers); setCountry(hero.country); setAddress(hero.address); setPhoto(hero.photo); } const submit = (e) => { e.preventDefault(); const form = document.getElementById('form'); console.log(form.checkValidity()); if (!form.checkValidity()) { form.classList.add('was-validated'); return; } const sendForm = { id: props.id, name, email, country, address, photo, powers: [] }; // sex: 객체 => male or female 의 string 으로 변환 for (let key in sex) { if (sex[key]) { sendForm.sex = key; } } // powers: 객체 => 스트링 배열로 변환 for (let key in powers) { if (powers[key]) { sendForm.powers.push(key); } } console.log(sendForm); api.put('/api/admin/hero', sendForm) .then(response => { console.log(response.data); // form 초기화 alert('수정되었습니다'); }); } const handleUpload = (e) => { e.preventDefault(); // 선택된 화일이 없으면 리턴 console.log(e.target.files); if (!e.target.files || e.target.files.length === 0) { return; } const formData = new FormData(); formData.append('photo', e.target.files[0], e.target.files[0].name); api.post('/api/admin/photo', formData) .then(response => { console.log(response.data); setPhoto(response.data.data); }); } return ( <> <form onSubmit={submit} noValidate id="form"> <div className="form-group mt-1"> <label htmlFor="name">Name</label> <input type="text" className="form-control" placeholder="Enter Name" id="name" value={name} onChange={(e) => setName(e.target.value)} required minLength="3" maxLength="10" /> <div className="invalid-feedback"> 3 ~ 10 사이의 이름을 입력하세요. </div> </div> <div className="form-group mt-1"> <label htmlFor="email">Email Address</label> <input type="email" className="form-control" placeholder="Enter Email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <div className="invalid-feedback"> 이메일 형식을 입력하세요. </div> </div> <div className="d-flex flex-column mt-1"> <div>성별</div> <div> <div className="form-check form-check-inline"> <input className="form-check-input" type="radio" name="sex" value="male" id="male" checked={sex.male} onChange={(e) => setSex({male: e.target.checked, female: !e.target.checked})} /> <label className="form-check-label" htmlFor="male">남자</label> </div> <div className="form-check form-check-inline"> <input className="form-check-input" type="radio" name="sex" value="female" id="female" checked={sex.female} onChange={(e) => setSex({male: !e.target.checked, female: e.target.checked})} /> <label className="form-check-label" htmlFor="female">여자</label> </div> </div> </div> <div className="form-group mt-1"> <label htmlFor="country">country</label> <select className="form-control" id="country" value={country} onChange={(e)=>setCountry(e.target.value)} required> <option value=""></option> <option value="Japan">Japan</option> <option value="American">American</option> <option value="Korean">Korean</option> </select> <div className="invalid-feedback"> 국가를 선택하세요. </div> </div> <div className="form-group mt-1"> <label htmlFor="address">Address</label> <textarea className="form-control" placeholder="Enter address" id="address" rows="3" value={address} onChange={(e)=>setAddress(e.target.value)}></textarea> </div> <div className="d-flex flex-column mt-1"> <div>powers</div> <div> <div className="form-check form-check-inline"> <input type="checkbox" value="flying" className="form-check-input" id="flying" checked={powers.flying} onChange={(e) => setPowers({...powers, flying: e.target.checked})}/> <label className="form-check-label" htmlFor="flying">flying</label> </div> <div className="form-check form-check-inline"> <input type="checkbox" value="penetration" className="form-check-input" id="penetration" checked={powers.penetration} onChange={(e) => setPowers({...powers, penetration: e.target.checked})} /> <label className="form-check-label" htmlFor="penetration">penetration</label> </div> <div className="form-check form-check-inline"> <input type="checkbox" value="hacking" className="form-check-input" id="hacking" checked={powers.hacking} onChange={(e) => setPowers({...powers, hacking: e.target.checked})}/> <label className="form-check-label" htmlFor="hacking">hacking</label> </div> <div className="form-check form-check-inline"> <input type="checkbox" value="strength" className="form-check-input" id="strength" checked={powers.strength} onChange={(e) => setPowers({...powers, strength: e.target.checked})} /> <label className="form-check-label" htmlFor="strength">strength</label> </div> </div> </div> <div className="d-flex flex-column mt-3 align-items-start"> <div>사진등록</div> <div className="custom-file"> <input type="file" className="custom-file-input" id="customFile" accept="image/*" onChange={handleUpload} /> <label className="custom-file-label" htmlFor="customFile">Choose file</label> </div> { photo ? <img src={photo} alt={name} style={{width: '200px'}} /> : '' } </div> <div className="m-3 d-flex justify-content-center"> <button type="submit" className="btn btn-outline-primary">수정</button> </div> </form> <p> {JSON.stringify({ name, email, sex, country, address, powers, photo })} </p> </> ) } |
데이터를 가져와서 바인딩을 하게 되면 그 다음부터는 Register 컴포넌트와 거의 동일하다. 마지막에 submit 시 post가 아니고 put 메서드만 달라지는것을 제외하고,,, REST api는 다음과 같다.
- api: /api/admin/hero
- method: PUT
- content-type: json
- request data: 서버에서는 id를 키로해서 다른 필드들에 대한 update가 수행된다. 그러므로 id는 필수키로 해서 변경이 필요한 필드를 추가로 전송해야 한다.