Aiko掲示板(Redux、Redux-Persist)
109106 ワード
私は李徳思が嫌いです。アクションを復元しても作成しても、やるべきことが多すぎます。私のREDEXショップも自分で作ったものではありません。
まず、Ridexで掲示板を実現し、画面内でしかプレイできませんでした。
輸入商団
//~~と書かれている部分がpage名なので、よく見てみましょう.
// 게시판
import * as React from 'react';
import axios from 'axios';
import { useState, useEffect } from 'react';
import Button from '@material-ui/core/Button';
import router from 'next/router';
import Link from 'next/link';
import { useSelector, useDispatch } from 'react-redux';
import {selectRow} from '../_redux/boardReducer';
import styles from '../styles/Board.module.css';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
const handleLogin = () => {
const url = '/api/notice-board/files';
const config = {
header: {
'content-type': 'application/json',
},
};
(async () => {
try {
await get(url, config);
} catch (err) {
console.log(err);
}
})();
};
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
marginTop: theme.spacing(2),
},
},
}));
export default function board() {
//for pagination
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostPerPage] = useState(10);
useEffect(() => {
const fetchPosts = async () => {
const res = await axios.get('http://jsonplaceholder.typicode.com/posts');
setPosts(res.data);
}
fetchPosts();
}, []);
console.log(posts)
//get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);
const {inputData} = useSelector((state) => state.boardReducer);
const {lastId} = useSelector((state) => state.boardReducer);
const dispatch = useDispatch();
const selectContent = (id) => {
dispatch(selectRow(id))
};
const handleChange = (e) => {
setRow(e.target.value)
};
const [row, setRow] = useState('');
const classes = useStyles();
return (
<>
<div className={styles.desc}>
<h2 className={styles.aikoBoard}>AIKO notice board</h2>
<div style={{marginRight:'30px'}}>
<FormControl variant="outlined" className={styles.formControl}>
<InputLabel htmlFor="outlined-age-native-simple">Rows</InputLabel>
<Select
native
value={row}
onChange={handleChange}
label="Age"
>
<option aria-label="None" value="" />
<option value={10}>10</option>
<option value={20}>20</option>
<option value={30}>30</option>
</Select>
</FormControl>
</div>
</div>
<div>
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
<th className={styles.th} style={{width:'7%'}}>No.</th>
<th className={styles.th} style={{width:'50%', textAlign:'left'}}>Title</th>
<th className={styles.th} style={{width:'20%', textAlign:'left'}}>Posted by</th>
<th className={styles.th} style={{width:'15%', textAlign:'left'}}>Date</th>
</tr>
</thead>
<tbody className={styles.tbody}>
<tr>
<td></td>
<td></td>
</tr>
{
lastId !== 0 ?
inputData.map(rowData => (
rowData.id !== '' &&
<tr>
<td className={styles.td} onClick={() => selectContent(rowData.id)}><Link href='/innerPost'><a>{rowData.id}</a></Link></td>
<td className={styles.td} style={{textAlign:'left'}} onClick={() => selectContent(rowData.id)}><Link href='/innerPost'><a>{rowData.title}</a></Link></td>
<td className={styles.td} style={{textAlign:'left'}}>{rowData.name}</td>
<td className={styles.td} style={{textAlign:'left'}}>{rowData.date}</td>
</tr>
)) :
<tr>
<td className={styles.td}></td>
<td className={styles.td}>작성된 글이 없습니다.</td>
</tr>
}
</tbody>
</table>
</div>
<div className={styles.postButtonContainer}>
<Button variant="contained" color="primary" style={{
width:'100px', height:'50px', borderRadius:'15px'}}
onClick={()=>{router.push('/writePost')}}>
NEW POST
</Button>
</div>
<div style={{ height: 300, width: '30%' }}>
<div onClick={handleLogin}> 파일 다운로드 테스트 </div>
</div>
</>
);
}
//글 작성
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import styles from '../styles/WritePost.module.css';
import {useState} from 'react';
import Button from '@material-ui/core/Button';
import axios from 'axios';
import {useDispatch} from 'react-redux';
import {dataSave} from '../_redux/boardReducer';
import router from 'next/router';
export default function writePost() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [name, setName] = useState('');
const [file, setFile] = useState(null);
const titleChange = (e) => {
setTitle(e.target.value);
};
const contentChange = (e) => {
setContent(e.target.value);
};
const handleFile = (e) => {
setFile(e.target.files);
};
const nameChange = (e) => {
setName(e.target.value);
};
const dispatch = useDispatch();
const upload = () => {
const formData = new FormData();
const url = '/api/notice-board/write';
formData.append("title", title);
formData.append("content", content);
for (let i=0; i<3; i++) {
formData.append("file", file[i]);
}
const config = {
headers: {
"content-type" : "multipart/form-data"
},
};
axios.post(url, formData, config)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error)
})
};
const date = new Date();
const onSave = () => {
const _inputData = {
id: '',
title:title,
content: content,
name: name,
date: Date.toLocaleString()
}
dispatch(dataSave(_inputData))
setTitle('')
setContent('')
setName('')
router.push('/board')
upload();
}
return (
<>
<div className={styles.writeBoard}>
<h2 style={{color:'#3F51B5', paddingTop:'20px', paddingLeft:'15%'}}>New Post</h2>
<div className={styles.titleName} style={{marginBottom:'20px'}}>
<div style={{width:'50%'}}>
<h4 style={{color:'#656565'}}>Title</h4>
<input className={styles.titleInput} type="text" value={title} placeholder="제목을 입력해주세요" onChange={titleChange}/>
</div>
<div style={{width:'20%'}}>
<h4 style={{color:'#656565'}}>Name</h4>
<input className={styles.nameInput} type="text" value={name} placeholder="이름을 입력해주세요" onChange={nameChange}/>
</div>
</div>
<div className={styles.contentBox}>
<div style={{width:'70%'}}>
<h4 style={{color:'#656565'}}>Content</h4>
<textarea className={styles.contentInput} type="text" value={content} placeholder="내용을 입력해주세요" onChange={contentChange} />
<div className={styles.fileSubmit}>
<label className={styles.fileLabel} onChange={handleFile}>
<input type="file" multiple style={{display:'none'}}/>
+ Attach File
</label>
<Button variant="contained" color="primary" style={{
width:'100px', height:'50px', borderRadius:'15px'}} onClick={onSave}>
SUBMIT
</Button>
</div>
</div>
</div>
</div>
</>
)
}
//게시글 확인
import react from 'react';
import styles from '../styles/innerPost.module.css';
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import { useState } from 'react';
import {useDispatch , useSelector} from 'react-redux';
import router from 'next/router';
import {editContent, removeContent} from '../_redux/boardReducer.js';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing(2),
textAlign: 'center',
color: theme.palette.text.secondary,
},
}));
export default function innerPost() {
const { selectRowData } = useSelector(state => state.boardReducer);
const [title, setTitle] = useState(selectRowData.title);
const [content, setContent] = useState(selectRowData.content);
const [name, setName] = useState(selectRowData.name);
const [date, setDate] = useState(selectRowData.date);
const handleTitle = (e) => {
setTitle(e.target.value);
}
const handleContent = (e) => {
setContent(e.target.value);
}
const handleName = (e) => {
setName(e.target.value);
}
const dispatch = useDispatch();
const onChange = () => {
const _inputData = {
id: selectRowData.id,
title:title,
content: content,
name: name,
date : date
}
const url = '/api/notice-board/update-article'
const data = {
'num': selectRowData.id,
'title': JSON.stringify(title),
'content': JSON.stringify(content)
}
const config = {
headers: {
"content-type" : 'application/json'
}
};
axios.post(url, data, config)
.then((response)=> {
console.log(selectRowData.id)
})
.catch((error) => {
console.log(error)
});
dispatch(editContent(_inputData))
setTitle('');
setContent('');
setName('');
router.push('/board');
}
const onRemove = () => {
dispatch(removeContent(selectRowData.id))
setTitle('');
setContent('');
setName('');
const url = "/api/notice-board/delete-article";
const data = {
'num' : selectRowData.id
}
const config ={
headers: {
'content-type' : 'application/json'
}
};
axios.post(url, data, config)
.then((response)=> {
console.log(data)
})
.catch((error)=> {
console.log(error)
});
router.push('/board');
}
const classes = useStyles();
return (
<>
<div className={styles.outerContainer}>
<div className={styles.titleName}>
<input className={styles.titleInput} onChange={handleTitle} value={title}/>
<p style={{fontSize:'17px', color:'#6F6A6A'}}>Posted by {name}, {date}</p>
</div>
<div className={styles.contentArea}>
<textarea className={styles.contentInput} value={content} onChange={handleContent} />
</div>
<div className={styles.reviseDelete}>
<Button variant="contained" color="primary"style={{
width:'100px', height:'50px', borderRadius:'15px', marginLeft:'15%' , backgroundColor:'#969696'}}
onClick={()=>{router.push('/board')}}>
LIST
</Button>
<div className={styles.align} style={{marginRight:'10%'}}>
<Button variant="contained" color="primary" style={{
width:'100px', height:'50px', borderRadius:'15px'}}
onClick={onChange}>
REVISE
</Button>
<Button variant="contained" color="primary" style={{
width:'100px', height:'50px', borderRadius:'15px', marginLeft:'10px', backgroundColor:'#D93D3D'}}
onClick={onRemove}>
DELETE
</Button>
</div>
</div>
<div className={styles.anotherPost} style={{marginTop:'15px'}}>
<div className={styles.previousPost}>
<Button size="small" className={classes.margin} style={{width:'20%'}}>
이전 글 보기
</Button>
<p style={{fontSize:'12px', color:'#9a9a9a', cursor:'pointer'}} onClick={function(){
alert('아직 기능 구현 중입니다.')
}}>사내식당 공지) 2021년 11월 건강한 식생활을 위한 채식주의 방침</p>
</div>
<div className={styles.nextPost}>
<Button size="small" className={classes.margin} style={{width:'20%'}}>
다음 글 보기
</Button>
<p style={{fontSize:'12px', color:'#9a9a9a', cursor:'pointer'}} onClick={function(){
alert('아직 기능 구현 중입니다.')
}}>2021년도 Aiko 프로젝트 감사 결과</p>
</div>
</div>
</div>
</>
);
}
// REDUX, REDUCER
const SAVE = 'DATA_SAVE';
const SELECT = 'DATA_SELECT';
const EDIT = 'DATA_EDIT';
const DELETE = 'DATA_DELETE';
const date = new Date();
export const dataSave = (inputData) => ({
type: SAVE,
inputData: {
id: inputData.id,
title: inputData.title,
content: inputData.content,
name: inputData.name,
date: date.toLocaleString()
}
});
export const selectRow = (id) => ({
type: SELECT,
inputData: {
id: id,
}
});
export const editContent = (inputData) => ({
type: EDIT,
inputData: {
id: inputData.id,
title: inputData.title,
content:inputData.content,
name: inputData.name,
date: inputData.date
}
});
export const removeContent = (id) => ({
type: DELETE,
inputData: {
id : id
}
});
const initialState = {
lastId: 0,
inputData: [
{
id:'',
title:'',
content:'',
name:'',
date: date.toLocaleString()
}
],
selectRowData: {}
};
export default function boardReducer(state = initialState, action) {
switch(action.type) {
case SAVE:
return {
lastId: state.lastId + 1,
inputData: state.inputData.concat({
...action.inputData,
id: state.lastId + 1,
})
}
case SELECT:
return {
...state,
selectRowData: state.inputData.find(row => row.id === action.inputData.id)
}
case EDIT:
return {
...state,
inputData: state.inputData.map(row => row.id === action.inputData.id ?
{...action.inputData} : row
),
selectRowData: {}
}
case DELETE:
return {
lastId: state.lastId === action.inputData.id ? state.lastId -1 : state.lastId,
inputData: state.inputData.filter(row=>
row.id !== action.inputData.id
),
selectRowData: {}
}
default:
return state;
}
};
逆伝播接続
Reference
この問題について(Aiko掲示板(Redux、Redux-Persist)), 我々は、より多くの情報をここで見つけました https://velog.io/@iamchho/Aiko-게시판-Redux-Redux-Persistテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol