일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 원티드
- JS
- es6
- 타입스크립트
- 리액트
- 알고리즘
- JavaScript
- array
- state
- CORS
- 프리온보딩
- 프론트엔드
- 프로그래머스
- til
- 파이어베이스
- v9
- react
- Reducer
- 컴포넌트
- react localStorage
- Frontend
- Redux
- 자바스크립트
- firebase
- 비트 연산자
- axios
- Component
- TypeScript
- 브라우저
- localstorage
- Today
- Total
도리쓰에러쓰
[React] DB없이 데이터 저장하고 싶으면 :: localStorage 2 본문
저번 게시물에 이어서 작성하겠습니다. (코드 참고)
state 데이터를 기억하게 하려면 2가지 방법이 있습니다.
1️⃣ 서버로 보내서 DB에 저장하기
2️⃣ 브라우저 저장공간인 localStorage에 저장하기
저는 localStorage에 데이터를 저장해보도록 하겠습니다.
localStorage에 대해서 알고 싶은 분들은 아래 게시물을 참고해주시면 되겠습니다.
1. 최근 본 상품 localStorage에 저장하여 UI 기능 만들기
1️⃣ Detail 페이지로 접속하면 localStorage에 있는 데이터 꺼냅니다.
useEffect(()=>{
let arr = localStorage.getItem('watched');
let numId = Number(id);
}, []);
* useEffect(()=>{}, []) : 페이지 load 시에만 실행되는 코드
* numId : parameter로 넘어온 id값을 string 타입에서 number 타입으로 변환
2️⃣ if문을 통해 arr가 null이면 localStorage에 'watched가 없을 때'의 실행 코드를,
null이 아니면 localStorage에 'watched가 있을 때'의 실행 코드를 작성할 예정입니다.
if(arr == null) {
// localStorage에 'watched'가 없을 때
} else {
// localStorage에 'watched'가 있을 때
}
3️⃣ arr가 null이면 localStorage에 강제로 'watched' 항목을 생성합니다.
if(arr == null) {
localStorage.setItem('watched', JSON.stringify([numId]));
}
4️⃣ arr가 null이 아니면 다음과 같이 동작하게 합니다.
else {
arr = JSON.parse(arr);
arr.push(numId);
arr = new Set(arr);
arr = [...arr];
localStorage.setItem('watched', JSON.stringify(arr));
}
* arr = JSON.parse(arr) : 데이터가 JSON 형태니까 object 형태로 변환 (따옴표 제거)
* arr.push(numId) : Array에 id값 push
* arr = new Set(arr) : set 자료형을 통해 arr 중복값 제거
* arr = [...arr] : 중괄호 {} 제거하고 대괄호 [] 붙여서 set 자료형을 array로 변환
* localStorage.setItem('watched', JSON.stringify(arr)) : Array를 다시 localStorage에 저장
- 처음 접속하면 아래 사진처럼 'watched' 항목이 강제로 생성됩니다.
- id가 1인 Detail 페이지로 접속하였더니, 'watched' 항목에 1이 추가되었고, 다시 접속해도 중복값이 나타나지 않습니다.
5️⃣ React-bootstrap 에서 가져온 코드를 통해 반복문으로 card 형태를 출력하겠습니다.
<div>
<Card>
<Card.Img variant="top" src="holder.js/100px180" />
<Card.Body>
<Card.Text>
Some quick example text to build on the card title and make up the bulk
of the card's content.
</Card.Text>
</Card.Body>
</Card>
</div>
6️⃣ arr을 저장하는 recentProd라는 state를 만들고, localStorage.setItem() 할 때마다 state 변경되도록 하겠습니다.
let [recentProd, setRecentProd] = useState([]);
useEffect(()=>{
let arr = localStorage.getItem('watched');
let numId = Number(id);
if(arr == null) {
localStorage.setItem('watched', JSON.stringify([numId]));
setRecentProd([numId]);
} else {
arr = JSON.parse(arr);
arr.push(numId);
arr = new Set(arr);
arr = [...arr];
localStorage.setItem('watched', JSON.stringify(arr));
setRecentProd(arr);
}
}, []);
7️⃣ recentProd가 null이면 아무것도 출력하지 않고,
null이 아니면 recentProd로 반복문을 돌려 RecentCard 컴포넌트를 출력하게 합니다.
동시에 props도 아래 코드와 같이 전달합니다.
<div>
<CardGroup style={{ width: '450px' }}>
{
recentProd === null
? null
: recentProd.map((a, i)=>{
return <RecentCard num={ a } products={ props.products }/>
})
}
</CardGroup>
</div>
8️⃣ RecentCard 컴포넌트는 아래 코드와 같이 생성하였습니다.
function RecentCard(props) {
return (
<Card style={{ textAlign: 'center', width: '130px' }}>
<Card.Img variant="top" className="Card-img" src={ process.env.PUBLIC_URL + '/images/img'+ (props.num+1) + '.jpg' } />
<Card.Body>
<Card.Text>
{ props.products[props.num].title }
</Card.Text>
</Card.Body>
</Card>
)
}
9️⃣ Card-img의 css는 아래 코드와 같이 설정하였습니다.
.Card-img {
width: 130px;
height: 200px;
}
- localhost:포트번호/detail/1에 접속하면
localStorage의 'watched' 항목에 [1]이 들어있어 아래 사진과 같이 출력됩니다.
- 마찬가지로 다시 localhost:포트번호/detail/2에 접속하면
localStorage의 'watched' 항목에 [1,2]가 들어있어 아래 사진과 같이 출력됩니다.
2. 전체 코드
Detail.js
import React, { useContext, useEffect, useState } from 'react';
import { Nav, Card, CardGroup } 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);
let [recentProd, setRecentProd] = useState([]);
useEffect(()=>{
let arr = localStorage.getItem('watched');
let numId = Number(id);
if(arr == null) {
localStorage.setItem('watched', JSON.stringify([numId]));
setRecentProd([numId]);
} else {
arr = JSON.parse(arr);
arr.push(numId);
arr = new Set(arr);
arr = [...arr];
localStorage.setItem('watched', JSON.stringify(arr));
setRecentProd(arr);
}
}, []);
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 : findProduct.id,
name : findProduct.title,
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>
<CardGroup style={{ width: '450px' }}>
{
recentProd === null
? null
: recentProd.map((a, i)=>{
return <RecentCard num={ a } products={ props.products }/>
})
}
</CardGroup>
</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 RecentCard(props) {
return (
<Card style={{ textAlign: 'center', width: '130px' }}>
<Card.Img variant="top" className="Card-img" src={ process.env.PUBLIC_URL + '/images/img'+ (props.num+1) + '.jpg' } />
<Card.Body>
<Card.Text>
{ props.products[props.num].title }
</Card.Text>
</Card.Body>
</Card>
)
}
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;
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: 250px;
height: 350px;
}
.Card-img {
width: 130px;
height: 200px;
}
.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);
}
}
* 이외의 코드는 이전 게시물에 작성된 코드와 일치합니다.
'코딩애플 (React) > 기초수업(코딩애플) - 3' 카테고리의 다른 글
[React] Node + Express 서버와 React 연동하기 (0) | 2022.02.07 |
---|---|
[React] DB없이 데이터 저장하고 싶으면 :: localStorage 1 (0) | 2022.02.06 |
[React] PWA 세팅해서 앱으로 발행하기 (0) | 2022.02.05 |
[React] 성능잡기2 :: 쓸데없는 재렌더링을 막는 memo (0) | 2022.02.02 |
[React] 성능잡기1 :: lazy loading / React devtools (0) | 2022.02.02 |