일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- react localStorage
- Redux
- es6
- v9
- 원티드
- 프로그래머스
- 컴포넌트
- 프론트엔드
- Component
- 프리온보딩
- 자바스크립트
- JavaScript
- CORS
- Frontend
- JS
- localstorage
- firebase
- 리액트
- 비트 연산자
- til
- 파이어베이스
- 알고리즘
- state
- axios
- 타입스크립트
- array
- 브라우저
- react
- Reducer
- TypeScript
- Today
- Total
도리쓰에러쓰
[React] Component 3개 중첩하여 만들고 state 전달하기 본문
저번 게시물에 이어서 작성하겠습니다. (코드 참고)
1. 3개가 중첩되어 있는 Component에 데이터바인딩
- 우선 App.js에 재고량 데이터를 생성합니다.
let [inventory, setInventory] = useState([10, 11, 12]);
- Detail.js에서 <p>재고 : ??? </p>를 생성합니다.
<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>
<p> 재고 : ???</p>
<button className='btn btn-danger'>주문하기</button>
<br />
<br />
<button className='btn btn-danger' onClick={ () => {
history.goBack();
}}>뒤로가기</button>
</div>
</div>
- 위의 코드에서 <p>재고 : ???</p> 라는 부분을 새로운 Component에 넣을 예정입니다.
function Info() {
return(
<p> 재고 : ???</p>
)
}
- 그리고 원래 <p>재고 : ???</p>가 있었던 부분을 Component로 대체하였습니다.
<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 />
<button className='btn btn-danger'>주문하기</button>
<br />
<br />
<button className='btn btn-danger' onClick={ () => {
history.goBack();
}}>뒤로가기</button>
</div>
</div>
- 지금 현재 Component가 App Component 안에 Detail Component가 있고,
Detail Component 안에 Info Component가 있는데
App Component에서 Info Component로 데이터 바인딩을 해보겠습니다.
- 우선 App Component에서 Detail Component로 데이터를 보내려면, App Component 안에 Detail Component 코드가 적혀있는 부분을 찾아 props를 작성해주시면 됩니다. (App.js)
<Detail products={ products } inventory={ inventory }/>
- 현재 데이터가 Detail Component까지 왔으니, Detail Component에서 Info Component로 데이터를 보내면 됩니다. 그러기 위해서 Detail Component 안에 Info Component 코드가 적혀있는 부분을 찾아 props를 작성해주시면 됩니다. (Detail.js)
<Info id={ findProduct.id } inventory={ props.inventory }> </Info>
- 최종적으로 Info Component에서 데이터바인딩을 해주시면 됩니다.
function Info(props) {
return(
<p> 재고 : { props.inventory[props.id] }</p>
)
}
- 그럼 아래의 화면과 같이 재고량이 출력된 것을 확인할 수 있습니다.
2. [주문하기] 버튼을 클릭하면 재고량 -1이 되려면?
- 아래 코드처럼 [주문하기] 버튼 이벤트를 생성하여 setInventory()를 호출하면 될까요?
<button className='btn btn-danger' onClick={ () => { setInventory() } }>주문하기</button>
* 위 코드처럼 setInventory() 함수를 호출할 수 없습니다. 왜냐하면, setInventory() 함수는 부모 Component인 <App />이 가지고 있기 때문입니다. inventory state를 변경하려면 props를 통해 전송하면 됩니다.
- App.js에서 Detail.js로 setInventory()를 전송합니다.
<Detail products={ products } inventory={ inventory } setInventory={ setInventory } />
- [주문하기] 버튼 이벤트에 inventoryOut()이라는 함수를 호출합니다.
<button className='btn btn-danger' onClick={ () => { inventoryOut() } }>주문하기</button>
- 아래 코드와 같이 state를 조작하여 재고량 -1를 하면 됩니다.
function inventoryOut() {
let id = findProduct.id;
let copyArr = [...props.inventory];
copyArr[id] = copyArr[id] - 1;
props.setInventory(copyArr);
}
* state 조작하는 방법
1) state의 사본 만들기
2) 사본 변경
3) 사본을 변경함수에 넣기
- 다음과 같이 [주문하기] 버튼을 누르면 재고량이 줄어드는 것을 확인할 수 있습니다.
< 오늘의 결론 >
1. 하위 컴포넌트가 몇개든 데이터를 전송하려면 props를 씁니다.
( 부모 컴포넌트 -> 자식 컴포넌트 -> 그 자식 컴포넌트 ··· )
2. 하위 컴포넌트가 상위 컴포넌트 state변경하려면 state 변경함수 사용합니다.
그게 상위 컴포넌트에 있으면 props로 전송하여 사용하면 됩니다.
3. 전체 코드
(shop 프로젝트의 모든 코드입니다.)
App.js
/* eslint-disable */
import React, { useState } from 'react'
import { Navbar, Container, Nav, NavDropdown, Button } from 'react-bootstrap';
import './App.css';
import data from './data.js';
import Detail from './Detail.js';
import axios from 'axios';
import { Link, Route, Switch } from 'react-router-dom';
function App() {
let [products, setProducts] = useState(data);
let [inventory, setInventory] = useState([10, 11, 12]);
return (
<div className="App">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand href="#home">Saint Laurent</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Nav.Link as={Link} to="/">Home</Nav.Link>
<Nav.Link as={Link} to="/detail">Detail</Nav.Link>
<NavDropdown title="Dropdown" id="basic-nav-dropdown">
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<Switch>
<Route exact path="/">
<div className='Jumbotron'>
<h1>20% Season Off</h1>
<p>
This is a simple hero unit, a simple jumbotron-style component for calling
extra attention to featured content or information.
</p>
<p>
<Button variant="primary">Learn more</Button>
</p>
</div>
<div className='container'>
<div className='row'>
{
products.map((a, i) => {
return <Card products={a} num={i} key={i}/>
})
}
</div>
<button className='btn btn-primary' onClick={()=>{
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((result)=>{
setProducts([...products, ...result.data])
})
.catch(()=>{
console.log('fail');
})
}}>더보기</button>
</div>
</Route>
<Route path="/detail/:id">
<Detail products={ products } inventory={ inventory } setInventory={ setInventory } />
</Route>
<Route path="/:id">
<div>아무말</div>
</Route>
</Switch>
</div>
);
}
function Card(props) {
return(
<div className='col-md-4'>
<img className="img" src={ 'images/img'+ (props.num + 1) +'.jpg' } />
<h4>{ props.products.title }</h4>
<p>{ props.products.content }</p>
<p>{ props.products.price }</p>
</div>
)
}
export default App;
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.Jumbotron {
background-color: #EBECF0;
height: 200px;
padding-top: 25px;
background-image: url('./background.jpg');
background-size: cover;
color: #FAFAFA;
}
.img {
position: relative;
width: 80%;
height: 300px;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
data.js
export default [
{
id : 0,
title : "Black Jacket",
content : "Born in Paris",
price : 6000000
},
{
id : 1,
title : "White Bag",
content : "Born in Paris",
price : 5000000
},
{
id : 2,
title : "Black Bag",
content : "Born in Paris",
price : 4000000
},
]
Detail.js
import React, { useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import './Detail.scss';
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('');
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);
}
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() } }>주문하기</button>
<br />
<br />
<button className='btn btn-danger' onClick={ () => {
history.goBack();
}}>뒤로가기</button>
</div>
</div>
</div>
)
}
function Info(props) {
return(
<p> 재고 : { props.inventory[props.id] }</p>
)
}
export default Detail;
Detail.scss
$mainColor : #ff0000;
.red {
color: $mainColor;
}
@mixin func() {
background: #eeeeee;
padding: 20px;
border-radius: 5px;
max-width: 500px;
width: 100%;
margin: auto;
p {
margin-bottom: 0;
}
}
.my-alert {
@include func();
}
.my-alert-red {
@extend .my-alert;
background: #fa8072;
}
index.html에서 <head> 부분에 bootstrap 코드 적용
<!-- BootStrap -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous"
/>
'코딩애플 (React) > 기초수업 (코딩애플) - 2' 카테고리의 다른 글
[React] Ajax 요청방법 - 2 (0) | 2022.01.20 |
---|---|
[React] React에서의 Ajax 요청 방법 / axios 설치 (0) | 2022.01.19 |
[React] Lifecycle Hook(예전 문법) vs useEffect(요즘 문법) (0) | 2022.01.18 |
[React] SASS 개념, 설치 및 기본 문법 (0) | 2022.01.17 |
[React] styled-components를 이용한 class없는 CSS 스타일링 (0) | 2022.01.12 |