main
detail
React
Redux
JavaScript
├── public
└── src
├── components
├── routes
├── assets ── lib
└── styles
store.js
import { createStore } from 'redux';
const ADD = 'ADD';
const DELETE = 'DELETE';
const CLEAR = 'CLEAR';
const addWish = (text) => {
return {
type: ADD,
text,
};
};
const deleteWish = (id) => {
return {
type: DELETE,
id,
};
};
const clearAll = () => {
return {
type: CLEAR,
};
};
JSON.parse(localStorage.getItem('wishes')) ||
localStorage.setItem('wishes', JSON.stringify([]));
const reducer = (
state = JSON.parse(localStorage.getItem('wishes')),
action
) => {
switch (action.type) {
case ADD:
const addItem = [...state, { text: action.text, id: Date.now() }];
localStorage.setItem('wishes', JSON.stringify(addItem));
return addItem;
case DELETE:
const deleteItem = state.filter((toDo) => toDo.id !== action.id);
localStorage.setItem('wishes', JSON.stringify(deleteItem));
return deleteItem;
case CLEAR:
localStorage.setItem('wishes', JSON.stringify([]));
return [];
default:
return state;
}
};
const store = createStore(reducer);
export const actionCreators = {
addWish,
deleteWish,
clearAll,
};
export default store;
만약 로컬스토리지에 wishes 필드가 존재한다면 그 값을 가져오고, 없다면 빈 배열을 기본값으로 설정하여 로컬스토리지에 등록함.
state의 기본값은 JSON.parse(localStorage.getItem('wishes'))
= 배열
- localStorage.setItem('wishes', JSON.stringify(바뀐state)
- return (바뀐state)
Home.jsx
import { useState } from 'react';
import { connect } from 'react-redux';
import { actionCreators } from '../store';
import { Link } from 'react-router-dom';
import { Container, StyledHeader, StyledMain } from '../styles/HomeStyle';
import { ReactComponent as Plus } from '../assets/plus.svg';
import { ReactComponent as Clear } from '../assets/clear.svg';
import Wish from '../components/Wish';
const Home = ({ wishes, addWishes, clearAll }) => {
const [text, setText] = useState('');
const onChangeInput = (e) => {
setText(e.target.value);
};
const onSubmitForm = (e) => {
e.preventDefault();
addWishes(text);
setText('');
};
const clearLocal = () => {
clearAll();
};
return (
<>
<Container>
<StyledHeader>
<h1 className="header-logo">
<Link to="/">WISH LIST</Link>
</h1>
<form onSubmit={onSubmitForm}>
<input
type="text"
value={text}
onChange={onChangeInput}
className="wish-input"
placeholder="If you sincerely hope, It will come true."
/>
<button className="wish-button">
<Plus />
</button>
</form>
</StyledHeader>
<StyledMain>
<ul className="wish-list">
{wishes.map((wish) => (
<Wish text={wish.text} id={wish.id} key={wish.id} />
))}
</ul>
<button className="clear-button" onClick={clearLocal}>
<Clear />
</button>
</StyledMain>
</Container>
</>
);
};
const mapStateToProps = (state) => {
return { wishes: state };
};
const mapDispatchToProps = (dispatch) => {
return {
addWishes: (text) => {
dispatch(actionCreators.addWish(text));
},
clearAll: () => {
dispatch(actionCreators.clearAll());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
원래대로라면 useSelector, useDispatch 훅을 사용하는 것이 더 편리하지만,
react-redux의 핵심 함수인 connect
의 원리에 대해 이해하고자 이 방법을 선택함.
-> HOC(Higher-order component)임.
connect의 첫번째 인자로 mapStateToProps
가 들어감.
-> store의 state를 wishes 라는 Home 컴포넌트의 props
로 넣어줌.
Detail.jsx
import { useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Container, StyledMain } from '../styles/HomeStyle';
import {
StyledDetailHeader,
StyledSpan,
DetailInfo,
} from '../styles/DetailStyle';
import { actionCreators } from '../store';
import Deleted from '../components/Deleted';
import { ReactComponent as Delete } from '../assets/delete.svg';
const Detail = ({ wish, deleteTodo }) => {
const { id } = useParams();
const wishText = wish.find((wish) => wish.id === parseInt(id));
const onClickButton = () => {
deleteTodo(id);
console.log(wish);
};
const writtenDate = new Date(+id);
const year = writtenDate.getFullYear();
const month = writtenDate.getMonth() + 1;
const date = writtenDate.getDate();
const writtenDateString = `${year}-${month.toString().padStart(2, '0')}-${date
.toString()
.padStart(2, '0')}`;
return (
<>
<Container>
<StyledDetailHeader>
<h1 className="header-logo">
<span className="header-span">
{' '}
“ If you sincerely hope, It will come true. “
</span>
<Link to="/">WISH LIST</Link>
</h1>
</StyledDetailHeader>
<StyledMain>
{wishText ? (
<>
<StyledSpan>I wish ...</StyledSpan>
<DetailInfo>
<h2 className="wish-title">{wishText.text}</h2>
<h5 className="wish-date">
<span className="date-title">written date </span>{' '}
{writtenDateString}
</h5>
<button
className="detail-delete-button"
onClick={onClickButton}
>
<Delete />
</button>
</DetailInfo>
</>
) : (
<Deleted />
)}
</StyledMain>
</Container>
</>
);
};
const mapStateToProps = (state) => {
return {
wish: state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
deleteTodo: (id) => dispatch(actionCreators.deleteWish(+id)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Detail);
id를 useParams()로 가져온 후, new Date(id)를 해서 작성 날짜를 렌더링함.
만약 wishText가 없다면 -> 이미 state(=wish)에서 삭제된 필드임.
-> Deleted 컴포넌트를 렌더링함.
참고로, useParams의 결과는 숫자도 string이 됨.(주의!)