다른 온라인 강의/생활코딩- React 2022년 개정판

끝) update, delete

backend dev 2024. 6. 18.

UPDATE 진행할 작업

 

update 기능 추가하기.

 

 

contextControl = <li><a href={"/update"+id} onClick={event =>{
    event.preventDefault();
    setMode('UPDATE');
}}>Update</a></li>
else if (mode === 'UPDATE') {
    let topic = topics.find(p => p.id === id); // topic이 존재하는지 예외처리는 여기서 스킵
    content = <Update title={topic.title} body={topic.body} onUpdate={(title,body)=>{ // Update는 기존의 값을 볼 필요가 있으므로 Update 컴포넌트는 기존의값을 가지는 props를 가져야한다.
        // 그래야 컴포넌트를 실행할때 [렌더링할때] 해당 props를 사용하여 렌더링해줄수있기 떄문.
        //onUpdate에는 수정된 title,body를 이용하여 적용하는 코드를 짜면된다.
        const updateTopic = {id: id, title: title, body: body};
        const newTopics = [...topics];

        for(let i=0;i<newTopics.length;i++){ // 해당 토픽을 찾아서 갈아끼운다.
            if (newTopics[i].id === id) {
                newTopics[i] = updateTopic;
                break;
            }
        }

        setTopics(newTopics); // topics state 변경

        setMode('READ');

    }}></Update>
}

 

function Update(props) {
    const [title, setTitle] = useState(props.title);
    const [body, setBody] = useState(props.body);

    return (
        <article>
            <h2>Update</h2>
            <form onSubmit={(event) => {
                event.preventDefault();
                // input 요소는 폼의 일부로서, 폼의 submit 이벤트가 발생하면 폼 전체가 이벤트의 타겟이 됩니다
                const title = event.target.title.value;  // form 태그안의 name이 title인 태그의 value를 가져오는 코드
                const body = event.target.body.value;
                props.onUpdate(title, body);
            }}>
                <p><input type="text" name="title" placeholder="title" value={title} onChange={event => {
                    // onChange는 값이 변경될때 실행될 함수를 설정한다.
                    // event.target은 해당 이벤트가 발생하고있는 input 태그이다.
                    setTitle(event.target.value);
                }} /></p>
                <p><textarea name="body" placeholder="body" value={body} onChange={event =>{
                    setBody(event.target.value);
                }}></textarea></p>
                <p><input type="submit" value="Update"/></p>
            </form>
        </article>
    )
}

 

 

props는 외부자가 내부로 주는것

 

state는 내부자가 사용하는 데이터

state는 컴포넌트안에서 바뀌어도 된다.

 

<p><input type="text" name="title" placeholder="title" value={props.title}/></p>
<p><textarea name="body" placeholder="body" value={props.body}></textarea></p>

props으로 전달된값을 이용해서  value를 채우면 해당 input 태그의 값을 수정 불가능하다.

 

수정 불가능한 이유 GTP- >

 

props로 전달된 값을 input 태그나 textarea 태그의 value 속성으로 직접 바인딩하면, 해당 입력 필드의 값을 수정할 수 없게 됩니다. 그 이유는 다음과 같습니다.

React에서 input 태그나 textarea 태그의 값은 state에 의해 제어되는 것이 일반적입니다. 이를 "제어 컴포넌트(Controlled Component)"라고 합니다. 제어 컴포넌트에서는 state가 단일 출처(single source of truth)로 작용하며, state 값이 변경될 때마다 해당 값을 UI에 반영합니다.

그런데 만약 value 속성에 props로 전달된 값을 직접 바인딩하면, 해당 입력 필드의 값은 props에 의해 결정되므로 state와 별개로 작동하게 됩니다. 이렇게 되면 사용자가 입력 필드에 값을 입력해도 state가 업데이트되지 않기 때문에 UI가 변경되지 않습니다.

따라서 props로 전달된 값을 input 태그나 textarea 태그의 value 속성에 직접 바인딩하면, 사용자가 해당 입력 필드의 값을 수정할 수 없게 됩니다. 이는 React의 단방향 데이터 흐름(one-way data flow) 원칙에 위배됩니다.

대신, 올바른 방법은 해당 입력 필드의 값을 state로 관리하고, state 값이 변경될 때마다 업데이트되도록 하는 것입니다. 그리고 value 속성에는 state 값을 바인딩합니다. 이렇게 하면 사용자가 입력한 값이 state에 반영되어 UI가 업데이트됩니다

 

 

전체코드

import './App.css';
import Header from './Header';
// 사용자 정의 태그를 만들때는 대문자로 시작해야한다.

import {useState} from 'react'; // state를 사용하기위해서 react에서 제공하는 useState라는 훅을 사용해야한다.

function Nav(props) {

// JSX 문법을 이용하면 배열에 태그를 저장할수도있다.
// 리스트의 각각의 태그는 key라는 프롭(속성)을 가져야하고 그 값을 유니크해야한다.
    const lis = props.topics.map((p) => (
        <li key={p.id}>
            <a id={p.id} href={'/read/' + p.id} onClick={(event) => {
                event.preventDefault();
                props.onChangeMode(parseInt(event.target.id)); //event.target은 이벤트가 발생한 태그를 의미 , 속성의 값은 문자열이므로 숫자 타입으로 변경해준다.
            }}> {p.title}</a>
        </li>
    ))

    return (
        <nav>
            <ol>
                {lis}
            </ol>
        </nav>
    );
}

function Article(props) {
    return (
        <article>
            <h2> {props.title} </h2>
            {props.body}
        </article>
    );
}

function Create(props) {
    return ( // onSubmit 이라는 jsx 문법을 이용하여 submit 버튼이 눌렸을떄 발생할
        /*
        리액트에서는 html의 기본태그 또한 컴포넌트로 취급하며, 속성은 props(프롭)으로 취급한다.
        onSubmit은 리액트에서 제공하고 jsx 문법인 props 이다.
        form 태그에 onSubmit props 을 이용하여 submit버튼이 눌렸을때 발생하는 이벤트를 지정할 수 있다.
        form 태그는 submit이 되었을때 페이지가 리로딩 되므로 해당 태그의 기본 동작을 막고 이벤트 리스너 함수를 지정해준다.
         */
        <article>
            <h2>Create</h2>
            <form onSubmit={(event) => {
                event.preventDefault();
                // input 요소는 폼의 일부로서, 폼의 submit 이벤트가 발생하면 폼 전체가 이벤트의 타겟이 됩니다
                const title = event.target.title.value;  // form 태그안의 name이 title인 태그의 value를 가져오는 코드
                const body = event.target.body.value;
                props.onCreate(title, body);
            }}>
                <p><input type="text" name="title" placeholder="title"/></p>
                <p><textarea name="body" placeholder="body"></textarea></p>
                <p><input type="submit" value="Create"/></p>
            </form>
        </article>
    );
}

function Update(props) {
    const [title, setTitle] = useState(props.title);
    const [body, setBody] = useState(props.body);

    return (
        <article>
            <h2>Update</h2>
            <form onSubmit={(event) => {
                event.preventDefault();
                // input 요소는 폼의 일부로서, 폼의 submit 이벤트가 발생하면 폼 전체가 이벤트의 타겟이 됩니다
                const title = event.target.title.value;  // form 태그안의 name이 title인 태그의 value를 가져오는 코드
                const body = event.target.body.value;
                props.onUpdate(title, body);
            }}>
                <p><input type="text" name="title" placeholder="title" value={title} onChange={event => {
                    // onChange는 값이 변경될때 실행될 함수를 설정한다.
                    // event.target은 해당 이벤트가 발생하고있는 input 태그이다.
                    setTitle(event.target.value);
                }} /></p>
                <p><textarea name="body" placeholder="body" value={body} onChange={event =>{
                    setBody(event.target.value);
                }}></textarea></p>
                <p><input type="submit" value="Update"/></p>
            </form>
        </article>
    )
}

function App() {
    // App이라는 컴포넌트는 한번 실행되면 다시 실행되지않는다. 그래서 이벤트함수에서 값을 변경해도 App컴포넌트가 다시실행되지않으므로
    // return되는 요소의 값들이 바뀌지않는다.
    // 다시 컴포넌트를 실행하기위해 state를 사용한다. state를 이용하여 App 컴포넌트가 다시 실행되게끔 한다.
    //userState는 배열을 반환하고 첫번째 요소는 상태의값 데이터, 두번째요소는 상태의 값을 변경할때 사용하는 함수이다.
    // const _mode = useState('WELCOME'); // 상태 초기값은 설정해줘야한다.
    // const mode =_mode[0]; // 첫번째 요소는 상태의값 데이터
    // const setMode =_mode[1]; // 두번째요소는 상태의 값을 변경할때 사용하는 함수
    //위의 코드는 너무 길어지므로 아래와 같이 간단하게 사용가능
    const [mode, setMode] = useState('WELCOME');

    const [id, setId] = useState(null); // 상태의 기본값으로 null도 가능
    const [nextId, setNextId] = useState(4);
    const [topics, setTopics] = useState([
        {id: 1, title: 'html', body: 'html is ...'},
        {id: 2, title: 'css', body: 'cs is ...'},
        {id: 3, title: 'javascript', body: 'javascript is ...'}
    ])
    let content = null;
    let contextControl = null;  //

    if (mode === 'WELCOME') {
        // id state를 이용하여 내용을 수정해준다.
        content = <Article title="WELCOME WEB" body="HELLO , WEB"></Article>
    } else if (mode === 'READ') { // find로 id 상태값과 같은 토픽을 찾는다. -> find메소드는 반복문과 거의비슷하므로 반복문으로 구성해도된다.
        let topic = topics.find(p => p.id === id); // 이렇게 찾아쓰는게 토픽에 해당 인덱스가 없는것에서 좀더 안전하다.
        if (topic) {
            content = <Article title={topic.title} body={topic.body}></Article>
        } else {
            content = <Article title="not found" body="not found"></Article>
        }
        contextControl = <li><a href={"/update"+id} onClick={event =>{
            event.preventDefault();
            setMode('UPDATE');
        }}>Update</a></li>
    } else if (mode === 'CREATE') {
        content = <Create onCreate={(title, body) => { // Create 컴포넌트의 프롭(props)으로 함수를 전달한다.
            const newTopic = {id: nextId, title: title, body: body};
            const newTopics = [...topics]; // 원래 topics를 복사해서
            newTopics.push(newTopic); // 새로 추가한 토픽을 추가하고
            setTopics(newTopics); // topics 상태값을 변경해줘야 리액트가 상태변화감지를 하여 컴포넌트를 재실행한다.[다시 렌더링]

            // 글을 추가후 해당 글로 이동
            setMode('READ');
            setId(newTopic.id);

            setNextId(nextId + 1);
        }}></Create>
    } else if (mode === 'UPDATE') {
        let topic = topics.find(p => p.id === id); // topic이 존재하는지 예외처리는 여기서 스킵
        content = <Update title={topic.title} body={topic.body} onUpdate={(title,body)=>{ // Update는 기존의 값을 볼 필요가 있으므로 Update 컴포넌트는 기존의값을 가지는 props를 가져야한다.
            // 그래야 컴포넌트를 실행할때 [렌더링할때] 해당 props를 사용하여 렌더링해줄수있기 떄문.
            //onUpdate에는 수정된 title,body를 이용하여 적용하는 코드를 짜면된다.
            const updateTopic = {id: id, title: title, body: body};
            const newTopics = [...topics];

            for(let i=0;i<newTopics.length;i++){ // 해당 토픽을 찾아서 갈아끼운다.
                if (newTopics[i].id === id) {
                    newTopics[i] = updateTopic;
                    break;
                }
            }

            setTopics(newTopics); // topics state 변경

            setMode('READ');

        }}></Update>
    }
    return (
        <div className="App">
            <Header title="REACT" onChangeMode={() => {
                setMode('WELCOME') //상태의 값을 변경할때 사용하는 함수를 사용해준다.
            }}></Header>
            <Nav topics={topics} onChangeMode={(id) => {
                setMode('READ') //상태의 값을 변경할때 사용하는 함수를 사용해준다.
                setId(id) // 전달인자인 클릭된 a태그의 아이디값을 이용하여 id state를 변경해준다.
            }}></Nav>
            {content}
            <ul>
                <li><a href="/create" onClick={event => {
                    event.preventDefault();
                    setMode('CREATE');
                }}>Create</a></li>
                {contextControl}
            </ul>
        </div>
    );
}

export default App;

 

 


Delete 추가

 

// react에서 태그를 다룰때는 하나의 태그 안에서 다뤄져야한다. 즉 가장 외부 태그가 한개여야한다. ,  빈 태그를 이용하여 여러 태그를 묶을수있다.
contextControl = <>
    <li><a href={"/update" + id} onClick={event => {
        event.preventDefault();
        setMode('UPDATE');
    }}>Update</a></li>
    <li><input type="button" value="Delete" onClick={() =>{
        const newTopics = [];
        for(let i=0;i<topics.length;i++){
            if(topics[i].id !== id){
                newTopics.push(topics[i]); // 현재 삭제를 위해 선택된 토픽이 아닌것만 담는다.
            }
        }
        setTopics(newTopics); // 그 배열로 topics state 변경
        setMode('WELCOME');
    }}/> </li>
</>

 

 

 

 

전체코드

import './App.css';
import Header from './Header';
// 사용자 정의 태그를 만들때는 대문자로 시작해야한다.

import {useState} from 'react'; // state를 사용하기위해서 react에서 제공하는 useState라는 훅을 사용해야한다.

function Nav(props) {

// JSX 문법을 이용하면 배열에 태그를 저장할수도있다.
// 리스트의 각각의 태그는 key라는 프롭(속성)을 가져야하고 그 값을 유니크해야한다.
    const lis = props.topics.map((p) => (
        <li key={p.id}>
            <a id={p.id} href={'/read/' + p.id} onClick={(event) => {
                event.preventDefault();
                props.onChangeMode(parseInt(event.target.id)); //event.target은 이벤트가 발생한 태그를 의미 , 속성의 값은 문자열이므로 숫자 타입으로 변경해준다.
            }}> {p.title}</a>
        </li>
    ))

    return (
        <nav>
            <ol>
                {lis}
            </ol>
        </nav>
    );
}

function Article(props) {
    return (
        <article>
            <h2> {props.title} </h2>
            {props.body}
        </article>
    );
}

function Create(props) {
    return ( // onSubmit 이라는 jsx 문법을 이용하여 submit 버튼이 눌렸을떄 발생할
        /*
        리액트에서는 html의 기본태그 또한 컴포넌트로 취급하며, 속성은 props(프롭)으로 취급한다.
        onSubmit은 리액트에서 제공하고 jsx 문법인 props 이다.
        form 태그에 onSubmit props 을 이용하여 submit버튼이 눌렸을때 발생하는 이벤트를 지정할 수 있다.
        form 태그는 submit이 되었을때 페이지가 리로딩 되므로 해당 태그의 기본 동작을 막고 이벤트 리스너 함수를 지정해준다.
         */
        <article>
            <h2>Create</h2>
            <form onSubmit={(event) => {
                event.preventDefault();
                // input 요소는 폼의 일부로서, 폼의 submit 이벤트가 발생하면 폼 전체가 이벤트의 타겟이 됩니다
                const title = event.target.title.value;  // form 태그안의 name이 title인 태그의 value를 가져오는 코드
                const body = event.target.body.value;
                props.onCreate(title, body);
            }}>
                <p><input type="text" name="title" placeholder="title"/></p>
                <p><textarea name="body" placeholder="body"></textarea></p>
                <p><input type="submit" value="Create"/></p>
            </form>
        </article>
    );
}

function Update(props) {
    const [title, setTitle] = useState(props.title);
    const [body, setBody] = useState(props.body);

    return (
        <article>
            <h2>Update</h2>
            <form onSubmit={(event) => {
                event.preventDefault();
                // input 요소는 폼의 일부로서, 폼의 submit 이벤트가 발생하면 폼 전체가 이벤트의 타겟이 됩니다
                const title = event.target.title.value;  // form 태그안의 name이 title인 태그의 value를 가져오는 코드
                const body = event.target.body.value;
                props.onUpdate(title, body);
            }}>
                <p><input type="text" name="title" placeholder="title" value={title} onChange={event => {
                    // onChange는 값이 변경될때 실행될 함수를 설정한다.
                    // event.target은 해당 이벤트가 발생하고있는 input 태그이다.
                    setTitle(event.target.value);
                }}/></p>
                <p><textarea name="body" placeholder="body" value={body} onChange={event => {
                    setBody(event.target.value);
                }}></textarea></p>
                <p><input type="submit" value="Update"/></p>
            </form>
        </article>
    )
}

function App() {
    // App이라는 컴포넌트는 한번 실행되면 다시 실행되지않는다. 그래서 이벤트함수에서 값을 변경해도 App컴포넌트가 다시실행되지않으므로
    // return되는 요소의 값들이 바뀌지않는다.
    // 다시 컴포넌트를 실행하기위해 state를 사용한다. state를 이용하여 App 컴포넌트가 다시 실행되게끔 한다.
    //userState는 배열을 반환하고 첫번째 요소는 상태의값 데이터, 두번째요소는 상태의 값을 변경할때 사용하는 함수이다.
    // const _mode = useState('WELCOME'); // 상태 초기값은 설정해줘야한다.
    // const mode =_mode[0]; // 첫번째 요소는 상태의값 데이터
    // const setMode =_mode[1]; // 두번째요소는 상태의 값을 변경할때 사용하는 함수
    //위의 코드는 너무 길어지므로 아래와 같이 간단하게 사용가능
    const [mode, setMode] = useState('WELCOME');

    const [id, setId] = useState(null); // 상태의 기본값으로 null도 가능
    const [nextId, setNextId] = useState(4);
    const [topics, setTopics] = useState([
        {id: 1, title: 'html', body: 'html is ...'},
        {id: 2, title: 'css', body: 'cs is ...'},
        {id: 3, title: 'javascript', body: 'javascript is ...'}
    ])
    let content = null;
    let contextControl = null;  //

    if (mode === 'WELCOME') {
        // id state를 이용하여 내용을 수정해준다.
        content = <Article title="WELCOME WEB" body="HELLO , WEB"></Article>
    } else if (mode === 'READ') { // find로 id 상태값과 같은 토픽을 찾는다. -> find메소드는 반복문과 거의비슷하므로 반복문으로 구성해도된다.
        let topic = topics.find(p => p.id === id); // 이렇게 찾아쓰는게 토픽에 해당 인덱스가 없는것에서 좀더 안전하다.
        if (topic) {
            content = <Article title={topic.title} body={topic.body}></Article>
        } else {
            content = <Article title="not found" body="not found"></Article>
        }
        // react에서 태그를 다룰때는 하나의 태그 안에서 다뤄져야한다. 즉 가장 외부 태그가 한개여야한다. ,  빈 태그를 이용하여 여러 태그를 묶을수있다.
        contextControl = <>
            <li><a href={"/update" + id} onClick={event => {
                event.preventDefault();
                setMode('UPDATE');
            }}>Update</a></li>
            <li><input type="button" value="Delete" onClick={() =>{
                const newTopics = [];
                for(let i=0;i<topics.length;i++){
                    if(topics[i].id !== id){
                        newTopics.push(topics[i]); // 현재 삭제를 위해 선택된 토픽이 아닌것만 담는다.
                    }
                }
                setTopics(newTopics); // 그 배열로 topics state 변경
                setMode('WELCOME');
            }}/> </li>
        </>


    } else if (mode === 'CREATE') {
        content = <Create onCreate={(title, body) => { // Create 컴포넌트의 프롭(props)으로 함수를 전달한다.
            const newTopic = {id: nextId, title: title, body: body};
            const newTopics = [...topics]; // 원래 topics를 복사해서
            newTopics.push(newTopic); // 새로 추가한 토픽을 추가하고
            setTopics(newTopics); // topics 상태값을 변경해줘야 리액트가 상태변화감지를 하여 컴포넌트를 재실행한다.[다시 렌더링]

            // 글을 추가후 해당 글로 이동
            setMode('READ');
            setId(newTopic.id);

            setNextId(nextId + 1);
        }}></Create>
    } else if (mode === 'UPDATE') {
        let topic = topics.find(p => p.id === id); // topic이 존재하는지 예외처리는 여기서 스킵
        content = <Update title={topic.title} body={topic.body} onUpdate={(title, body) => { // Update는 기존의 값을 볼 필요가 있으므로 Update 컴포넌트는 기존의값을 가지는 props를 가져야한다.
            // 그래야 컴포넌트를 실행할때 [렌더링할때] 해당 props를 사용하여 렌더링해줄수있기 떄문.
            //onUpdate에는 수정된 title,body를 이용하여 적용하는 코드를 짜면된다.
            const updateTopic = {id: id, title: title, body: body};
            const newTopics = [...topics];

            for (let i = 0; i < newTopics.length; i++) { // 해당 토픽을 찾아서 갈아끼운다.
                if (newTopics[i].id === id) {
                    newTopics[i] = updateTopic;
                    break;
                }
            }

            setTopics(newTopics); // topics state 변경

            setMode('READ');

        }}></Update>
    }
    return (
        <div className="App">
            <Header title="REACT" onChangeMode={() => {
                setMode('WELCOME') //상태의 값을 변경할때 사용하는 함수를 사용해준다.
            }}></Header>
            <Nav topics={topics} onChangeMode={(id) => {
                setMode('READ') //상태의 값을 변경할때 사용하는 함수를 사용해준다.
                setId(id) // 전달인자인 클릭된 a태그의 아이디값을 이용하여 id state를 변경해준다.
            }}></Nav>
            {content}
            <ul>
                <li><a href="/create" onClick={event => {
                    event.preventDefault();
                    setMode('CREATE');
                }}>Create</a></li>
                {contextControl}
            </ul>
        </div>
    );
}

export default App;

 

 

 

 

 

 

댓글