도리쓰에러쓰

[React] Redux4 :: dispatch로 데이터 보내기 본문

코딩애플 (React)/기초수업(코딩애플) - 3

[React] Redux4 :: dispatch로 데이터 보내기

강도리 2022. 2. 1. 00:17

저번 게시물에 이어서 작성하겠습니다. (코드 참고)

 

[React] Redux3 :: state와 reducer가 더 필요하다면?

저번 게시물에 이어서 작성하겠습니다. (코드 참고) [React] Redux2 :: reducer / dispatch로 데이터 수정하기 저번 게시물에 이어서 작성하겠습니다. (코드 참고) [React] Redux1 :: props 대신 사용하기 저번 게.

dori-coding.tistory.com


1. dispatch()로 데이터 수정 요청할 때 데이터 보내기

1️⃣ Cart.js에서 버튼에 dispatch()에 payload 추가하기

dispatch({ type : 'type명', payload : '보낼데이터' })

<button onClick={()=>{ props.dispatch({ type : '수량증가', payload : {name:'kim'} }) }}>+</button>

 

 

2️⃣ reducer()에서 action.payload를 통해 데이터 사용하기

  * 보낸 데이터는 action 파라미터에 저장되어 있습니다.

function reducer(state = basicState, action) {
  if( action.type === '수량증가' ) {
    console.log(action.payload);
    return state;
  } else {
    return state
  } 
}

 

- console.log를 찍어보니 다음과 같이 데이터가 출력되는 것을 확인할 수 있습니다.


2. [주문하기] 버튼을 누르면 데이터에 상품 추가하기

- 이전에 만들어둔 Detail.js 화면이 있습니다.

  * 접속 경로 : localhost:포트번호/detail/2

- 위 화면(사진)에서 [주문하기] 버튼을 클릭하면 index.js에 있는 basicState에 데이터가 추가되도록 하겠습니다.

  * basicState 현재 데이터

let basicState = [
  { id : 0, name : 'Black Jacket', quan : 2 }, 
  { id : 1, name : 'White Bag', quan : 5 }
];

1️⃣ index.js에서 reducer()에 데이터 수정되는 방법 정의하기

function reducer(state = basicState, action) {
  if( action.type === '항목추가' ) {
    let copyArr = [...state];
    copyArr.push();
    return copyArr;
  } else if( action.type === '수량증가' ) {
    let copyArr = [...state];
    copyArr[0].quan++;
    return copyArr;
  } else if( action.type === '수량감소' ) {
    let copyArr = [...state];
    copyArr[0].quan--;
    return copyArr;
  } else {
    return state
  } 
}

- 기존 작성된 reducer()에서 조건이 '항목추가'일 때의 if문을 추가하였습니다.

- state 배열을 복사하여 복사한 배열에 ?? 값을 넣고, 복사한 배열을 return하는 if문입니다. ( ?? 값은 추후에 추가 예정 )

 

 

2️⃣ Detail.js에서 state를 prop화 해주는 것 작성하기

function store(state) {
    return {
        state : state.reducer,
        alertState : state.reducer2
    }
}

export default connect(store)(Detail)
// export default Detail;

 

- connect 함수도 import 해야 합니다.

import { connect } from 'react-redux';

 

 

3️⃣ [주문하기] 버튼 누르면 dispatch() 하여 데이터 함께 전송하기

<button className='btn btn-danger' onClick={ () => { 
    inventoryOut();
    props.dispatch({ type : '항목추가' , payload : {id:2, name:'Black Jacket', quan:1} });    
} }>주문하기</button>

 

 

4️⃣ index.js에서 reducer()에 데이터 추가하기

  * reducer()의 action 파라미터는 dispatch()할 때 보낸 object입니다.

  * 전송한 데이터를 사용하려면 : action.payload

function reducer(state = basicState, action) {
  if( action.type === '항목추가' ) {
    let copyArr = [...state];
    copyArr.push(action.payload);
    console.log(copyArr);
    return copyArr;
  } else if( action.type === '수량증가' ) {
    let copyArr = [...state];
    copyArr[0].quan++;
    return copyArr;
  } else if( action.type === '수량감소' ) {
    let copyArr = [...state];
    copyArr[0].quan--;
    return copyArr;
  } else {
    return state
  } 
}

 

- 데이터가 저장되어 있는 action.payload를 배열 copyArr에 추가하였고, console창에 출력해보니 아래의 사진처럼 데이터가 추가된 것을 확인할 수 있습니다.

 


💡 개발환경에서 새로고침하면 redux도 초기화되기 때문에, localhost:포트번호/cart 경로로 접속하면 아래의 사진처럼 데이터가 초기화된 것을 확인할 수 있습니다.

 

- 개발환경에서 페이지 이동 시 강제 새로고침 안되게 하려면?

  * Detail.js에서 [주문하기] 버튼에 history.push()를 추가하시면 됩니다. ( history.push()는 useHistory() Hook )

  * history.push() : 페이지 이동을 강제로 시켜줍니다.

<button className='btn btn-danger' onClick={ () => { 
    inventoryOut();
    props.dispatch({ type : '항목추가' , payload : {id:2, name:'Black Jacket', quan:1} }); 
    history.push('/cart');   
} }>주문하기</button>

 

- [주문하기] 버튼을 클릭하면,

 

localhost:포트번호/cart 로 이동되면서 데이터가 초기화되지 않고 나타나는 것을 확인할 수 있습니다.


🔔 위에 하드코딩으로 임시상품명 데이터를 추가시키지 않고 실제 상품명 데이터를 redux에 저장하기 (같은 상품이 이미 있으면 수량만 증가시키기)

 

1️⃣ Detail.js에 간단한 form 만들기

<div>
    <br /> <br />
    ID : <input type="number"></input> <br /> <br />
    제품명 : <input type="text"></input> <br /> <br />
    재고 : <input type="number"></input> <br /> <br />
    <button className='btn btn-dark'>주문하기</button>
</div>

 

 

2️⃣ useState()를 이용하여 변수 3개를 생성하기

let [idVal, setIdVal] = useState(0);
let [nameVal, setNameVal] = useState('');
let [quanVal, setQuanVal] = useState(0);

 

 

3️⃣ onChange() 이벤트리스너를 통해 input 태그에 입력되는 값을 변수에 저장하기

ID : <input type="number" onChange={ (e) => { setIdVal(e.target.value) } }></input> <br /> <br />
제품명 : <input type="text" onChange={ (e) => { setNameVal(e.target.value) } }></input> <br /> <br />
재고 : <input type="number" onChange={ (e) => { setQuanVal(e.target.value) } }></input> <br /> <br />

 

 

4️⃣ onClick() 이벤트리스너를 통해 dispatch()로 데이터를 넘기고,

  localhost:포트번호/cart 경로로 강제 페이지 이동을 시켜주기

<button className='btn btn-dark' onClick={ () => {
    props.dispatch({
        type : '주문하기',
        payload : {
            id : idVal,
            name : nameVal,
            quan : quanVal
        }
    });
    history.push('/cart');
} }>주문하기</button>

 

 

5️⃣ index.js에서 아래 코드과 같이 작성하기

function reducer(state = basicState, action) {
  if( action.type === '항목추가' ) {
	// 위 코드 참고
  } else if( action.type === '주문하기' ) {
    let copyArr = [...state];
    action.payload.id = Number(action.payload.id);
    action.payload.quan = Number(action.payload.quan);
    for(let i = 0; i < state.length; i++) {
      if( copyArr[i].id === action.payload.id ) {
        copyArr[i].quan++;
        break;
      } else if(i === state.length-1) {
        copyArr.push(action.payload);
      }
    }
    return copyArr;
  } else if( action.type === '수량증가' ) {
    // 위 코드 참고
  } else if( action.type === '수량감소' ) {
    // 위 코드 참고
  } else {
    return state
  } 
}

- [...state] : 배열 복사

- if문 : copyArr에 있는 id와 action.payload.id(input에서 입력했던 ID)가 일치하면 재고(quan) +1

- else문 : copyArr에 있는 id와 action.payload.id(input에서 입력했던 ID)가 일치하지 않으면 copyArr에 데이터 추가

 

- Detail.js에서 form창에 다음과 같이 입력하면,

 

- localhost:포트번호/cart로 페이지가 이동되면서 리스트가 추가된 것을 확인할 수 있습니다.

 

- 이전 페이지로 돌아가서, form창에 다음과 같이 입력하면

 

- localhost:포트번호/cart로 페이지가 이동되면서,

  ID가 1인 항목이 있기 때문에 재고 수가 1개 추가된 것을 확인할 수 있습니다.


3. 전체 코드

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import { BrowserRouter } from 'react-router-dom';

import { Provider } from 'react-redux';
import { combineReducers, createStore } from 'redux';

let alertState = true;

function reducer2(state = alertState, action) {
  if( action.type === 'alertClose' ) {
    state = false;
    return state;
  } else {
    return state;
  }
}

let basicState = [
  { id : 0, name : 'Black Jacket', quan : 2 }, 
  { id : 1, name : 'White Bag', quan : 5 }
];

function reducer(state = basicState, action) {
  if( action.type === '항목추가' ) {
    let copyArr = [...state];
    copyArr.push(action.payload);
    console.log(copyArr);
    return copyArr;
  } else if( action.type === '주문하기' ) {
    let copyArr = [...state];
    action.payload.id = Number(action.payload.id);
    action.payload.quan = Number(action.payload.quan);
    for(let i = 0; i < state.length; i++) {
      if( copyArr[i].id === action.payload.id ) {
        copyArr[i].quan++;
        break;
      } else if(i === state.length-1) {
        copyArr.push(action.payload);
      }
    }
    return copyArr;
  } else if( action.type === '수량증가' ) {
    let copyArr = [...state];
    copyArr[0].quan++;
    return copyArr;
  } else if( action.type === '수량감소' ) {
    let copyArr = [...state];
    copyArr[0].quan--;
    return copyArr;
  } else {
    return state
  } 
}

// let store = createStore(reducer);
let store = createStore(combineReducers({reducer, reducer2}));

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App />
      </Provider>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

Detail.js

import React, { useContext, useEffect, useState } from 'react';
import { Nav } from 'react-bootstrap';
import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import { inventoryContext } from './App.js';
import './Detail.scss';

import { CSSTransition } from "react-transition-group";
import { connect } from 'react-redux';

let Box = styled.div`
    padding : 20px;
`;

let Title = styled.h4`
    font-size : 25px;
    color : ${ props => props.color }
`;

function Detail(props) {

    let { id } = useParams();
    let history = useHistory();
    let findProduct = props.products.find(function(product) {
        return product.id = id;
    });

    let [alert, setAlert] = useState(true);
    let [inputData, setInputData] = useState('');

    let [pushTab, setPushTab] = useState(0);
    let [switchBtn, setSwitchBtn] = useState(false);

    let [idVal, setIdVal] = useState(0);
    let [nameVal, setNameVal] = useState('');
    let [quanVal, setQuanVal] = useState(0);

    useEffect(()=>{
        let timer = setTimeout(() => { setAlert(false) }, 2000);
        return ()=>{ clearTimeout(timer) }
    }, []);

    function inventoryOut() {
        let id = findProduct.id;
        let copyArr = [...props.inventory];
        
        copyArr[id] = copyArr[id] - 1;
        props.setInventory(copyArr);
    }

    let inventory = useContext(inventoryContext);
    
    return(
        <div className='container'>
            <Box>
                <Title className='red'>상세페이지</Title>
            </Box>

            <input onChange={ (e)=>{ setInputData(e.target.value) } }/>

            {
                alert === true
                ? (<div className='my-alert-red'>
                        <p>재고가 얼마 남지 않았습니다!</p>
                    </div>)
                : null
            }
            
            <div className='row'>
                <div className='col-md-6'>
                    <img className='img' src={ process.env.PUBLIC_URL + '/images/img'+ (Number(findProduct.id) + 1)+'.jpg' } />
                </div>
                <div className='col-md-6 mt-4'>
                    <h4 className='pt-5'>{ findProduct.title }</h4>
                    <p>{ findProduct.content }</p>
                    <p>{ findProduct.price }</p>
                    <Info id={ findProduct.id } inventory={ props.inventory }> </Info>
                    <button className='btn btn-danger' onClick={ () => { 
                        inventoryOut();
                        props.dispatch({ 
                            type : '항목추가' , 
                            payload : {
                                id : 2, 
                                name : 'Black Jacket', 
                                quan : 1
                            }
                        }); 
                        history.push('/cart');   
                    } }>주문하기</button>
                    <br />
                    <br />
                    <button className='btn btn-danger' onClick={ () => {
                        history.goBack();
                    }}>뒤로가기</button>
                </div>
                <div>
                    <br /> <br />
                    ID : <input type="number" onChange={ (e) => { setIdVal(e.target.value) } }></input> <br /> <br />
                    제품명 : <input type="text" onChange={ (e) => { setNameVal(e.target.value) } }></input> <br /> <br />
                    재고 : <input type="number" onChange={ (e) => { setQuanVal(e.target.value) } }></input> <br /> <br />
                    <button className='btn btn-dark' onClick={ () => {
                        props.dispatch({
                            type : '주문하기',
                            payload : {
                                id : idVal,
                                name : nameVal,
                                quan : quanVal
                            }
                        });
                        history.push('/cart');
                    } }>주문하기</button>
                </div>
            </div>

            <Nav className="mt-5" variant="tabs" defaultActiveKey="link-0">
                <Nav.Item>
                    <Nav.Link eventKey="link-0" onClick={ ()=>{ setSwitchBtn(false); setPushTab(0) } }>상품설명</Nav.Link>
                </Nav.Item>
                <Nav.Item>
                    <Nav.Link eventKey="link-1" onClick={ ()=>{ setSwitchBtn(false); setPushTab(1) } }>배송정보</Nav.Link>
                </Nav.Item>
            </Nav>
            
            <CSSTransition in={ switchBtn } classNames="wow" timeout={500}>
                <TabContent pushTab={ pushTab } setSwitchBtn={ setSwitchBtn }/>
            </CSSTransition>

        </div>
    )
}

function TabContent(props) {

    useEffect(()=>{
        props.setSwitchBtn(true);
    });

    if(props.pushTab === 0) {
        return <div>0번째 내용</div>
    } else if(props.pushTab === 1) {
        return <div>1번째 내용</div>
    } else if(props.pushTab === 2) {
        return <div>2번째 내용</div>
    }
}

function Info(props) {
    return(
        <p> 재고 : { props.inventory[props.id] }</p>
    )
}

function store(state) {
    return {
        state : state.reducer,
        alertState : state.reducer2
    }
}

export default connect(store)(Detail)
// export default Detail;

 

* 이외의 코드는 이전 게시물에 작성된 코드와 일치합니다.

Comments