owned this note
owned this note
Published
Linked with GitHub
---
tags: Example,
disqus: hackmd
---
# React - 留言板2
基本上是根據[React - 留言板](https://hackmd.io/pAypxgRBTzKBaN7m2X4Xug)來延續下去的。
主要為新增,編輯訊息、刪除訊息、以及將訊息保留畫面重整不遺失的功能。
因為前一張的資料結構關係,在編輯訊息時遇到同樣為送出按鈕,但是要如何克服我是要找到父留言的訊息還是子留言的訊息。
![我的留言資料結構畫面](https://i.imgur.com/QgoAtR1.png)
我的子留言的data是放在父留言的`responseArray`底下的。
因為我在新增子留言的時候直接把子留言的data放到父的`responseArray`,
這樣做導致我在編輯或刪除的時候必須去多傳一個props去判斷現在是父還是子要編輯或刪除。
### 理想改動
父留言的data結構一樣維持本來的樣子,但是子留言在新增時不是直接塞到父留言的`responseArray`,而是跟父留言一樣只是要多一個欄位這個欄位要放父留言的`id`
```javascript=
id: 1001, name: chris, time: '2020-07-10', text: 'hello'
id: 2001, parentId: 1001 name: chris, time: '2020-07-10', text: 'hello'
```
類似這樣的做法,然後再透過處理陣列把有`parentId`的留言放到對應的父留言的`responseArray`裡。
每次新增或刪除,即可以找到對應的父id然後重新把剩下對應到的子留言放入。
---
附上我目前在留言板2的更動
Home.js
```javascript=
import React from 'react';
import { connect } from 'react-redux';
import { addMessage } from '../redux/action/message';
import styled from 'styled-components';
import InputBox from '../component/InputBox';
import MessageContainer from '../component/MessageContainer';
class Home extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.handleSumbitMessage = this.handleSumbitMessage.bind(this);
}
handleSumbitMessage(e) {
const { addMessage, messageList } = this.props;
addMessage([...messageList, {
id: Date.now(),
...e,
}]);
}
componentDidMount() {
const { addMessage } = this.props;
const messageList = JSON.parse(localStorage.getItem('messageData'));
messageList && addMessage(messageList);
}
render(){
const {messageList} = this.props;
return(
<HomeWrap>
<Title> React 留言板 </Title>
<InputBox onSubmitMessage={this.handleSumbitMessage} />
<MessageContainer messageList={messageList} />
</HomeWrap>
);
}
}
const Title = styled.h1`
margin: 0 auto 15px;
color: #0CA0F9;
text-align: center;
`;
const HomeWrap = styled.div`
background: #BEDADB;
margin: 0 auto;
padding: 15px;
width: 700px;
`;
const mapStateToProps = (state) => ({
messageList: state.messageList,
});
const mapDispatchToProps = (dispatch) => ({
addMessage: (e) => { dispatch(addMessage(e)); },
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Home);
```
reducer -> message.js
```javascript=
import { combineReducers } from 'redux';
import {ADD_MESSAGE} from '../action/message';
const defaultMessage = {
id: 0,
name: 'chris',
text: '測試一號',
time: '1990-07-8 12:00:00',
responseArray: [],
}
function messageList(state = [defaultMessage], action) {
switch (action.type) {
case ADD_MESSAGE:
localStorage.setItem('messageData', JSON.stringify(action.info));
return action.info;
default:
return state;
}
}
const rootReducer = combineReducers({
messageList,
});
export default rootReducer;
```
InputBox.js
```javascript=
import React from 'react';
import styled from 'styled-components';
export default class InputBox extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
name: '',
text: '',
};
this.handleTextChange = this.handleTextChange.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleGetTime = this.handleGetTime.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleTextChange(e) {
this.setState({text: e.currentTarget.value});
}
handleNameChange(e) {
this.setState({name: e.currentTarget.value});
}
handleGetTime(fmt) {
const date = new Date();
const o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小時
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
handleSubmit() {
const {isShowInputBox, onSubmitMessage, isReset, isChild, id} = this.props;
const {name, text} = this.state;
if (!text) {
return;
}
onSubmitMessage({
name: name || '匿名人士',
text,
time: this.handleGetTime('yyyy-MM-dd hh:mm:ss'),
responseArray: [],
}, isChild, id);
// isReset,按下送出後需要將name跟text清空,除了編輯訊息
if (isReset) {
this.setState({
name: '',
text: ''
});
}
// 控制編輯訊息的輸入框狀態
isShowInputBox && isShowInputBox();
}
componentDidMount() {
this.setState({
name: this.props.propsName || '',
text: this.props.propsValue || '',
});
}
render() {
const {propsName} = this.props;
const {name, text} = this.state;
return (
<InputBoxWrap>
<Textarea placeholder="在這留下你想說的話" value={text} onChange={this.handleTextChange} />
<Nickname type="text" placeholder="您的大名" value={name} onChange={this.handleNameChange} disabled={propsName} />
<Submit onClick={this.handleSubmit}>確認送出</Submit>
</InputBoxWrap>
);
}
}
InputBox.defaultProps = {
isReset: true,
isChild: false,
};
const Submit = styled.button`
border: none;
border-radius: 5px;
height: 30px;
cursor: pointer;
transition: all .5s ease;
user-select: none;
&:focus {
outline: none;
}
&:hover {
background: #FEBFCF;
}
`;
const Nickname = styled.input`
margin-right: 20px;
border: none;
border-radius: 5px;
padding: 0 10px;
width: 150px;
height: 30px;
box-sizing: border-box;
&:focus {
outline: none;
}
`;
const Textarea = styled.textarea`
border: none;
border-radius: 5px;
padding: 10px;
width: 100%;
height: 200px;
font-size: 20px;
resize: none;
box-sizing: border-box;
&:focus {
outline: none;
}
`;
const InputBoxWrap = styled.div`
margin: 0 auto 20px;
width: 500px;
max-width: 100%;
`;
```
MessageContainer.js
```javascript=
import React from 'react';
import styled from 'styled-components';
import MessageItem from '../component/MessageItem';
export default class MessageContainer extends React.Component {
constructor(props) {
super(props);
this.props = props;
}
render() {
const {messageList} = this.props;
return (
<MessageWrap>
{
messageList.map((element, index) => {
return <MessageItem
key={index}
id={element.id}
name={element.name}
value={element.text}
time={element.time}
responseArray={element.responseArray}
/>
})
}
</MessageWrap>
);
}
}
const MessageWrap = styled.div`
margin: 0 auto;
padding: 10px;
width: 500px;
min-height: 300px;
`;
```
MessageItem.js
```javascript=
import React from 'react';
import { connect } from 'react-redux';
import { addMessage } from '../redux/action/message';
import styled from 'styled-components';
import InputBox from '../component/InputBox';
import MessageAtom from '../component/MessageAtom';
class MessageItem extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
isShowResponse: false,
}
this.handleSumbitMessage = this.handleSumbitMessage.bind(this);
this.handleToggleResponse = this.handleToggleResponse.bind(this);
this.handleEditMessage = this.handleEditMessage.bind(this);
}
handleSumbitMessage(e) {
const { addMessage, messageList, id } = this.props;
const cloneMessageList = Object.assign([], messageList);
cloneMessageList.find((element) => {
if (id === element.id) {
element.responseArray = [
...element.responseArray,
{
id: Date.now(),
name: e.name,
text: e.text,
time: e.time,
}
]
}
});
addMessage(cloneMessageList);
}
handleToggleResponse() {
const {isShowResponse} = this.state;
this.setState({isShowResponse: !isShowResponse});
}
handleEditMessage(e, isChild, id) {
const { addMessage, messageList } = this.props;
const cloneMessageList = Object.assign([], messageList);
if (isChild) {
cloneMessageList.map(list => {
list.responseArray.find(childList => {
if (id === childList.id) {
childList.text = e.text;
childList.time = e.time;
}
});
});
} else {
cloneMessageList.find(list => {
if (id === list.id) {
list.text = e.text;
list.time = e.time;
}
});
}
addMessage(cloneMessageList);
}
render(){
const {responseArray, name, time, value, id} = this.props
const {isShowResponse} = this.state;
return (
<MessageItemWrap>
<MessageAtom id={id} name={name} time={time} value={value} onSubmitMessage={this.handleEditMessage} />
{
responseArray && responseArray.map((element, index) => {
return (
<ResponseMessage key={index}>
<MessageAtom id={element.id} name={element.name} time={element.time} value={element.text} onSubmitMessage={this.handleEditMessage} isChild={true} />
</ResponseMessage>
);
})
}
<ResponseButton isShow={isShowResponse} onClick={this.handleToggleResponse}>發表回應</ResponseButton>
{
isShowResponse && <InputBox isShowInputBox={this.handleToggleResponse} onSubmitMessage={this.handleSumbitMessage} />
}
</MessageItemWrap>
);
}
}
const mapStateToProps = (state) => ({
messageList: state.messageList,
});
const mapDispatchToProps = (dispatch) => ({
addMessage: (e) => { dispatch(addMessage(e)); },
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(MessageItem);
const ResponseButton = styled.div`
display: inline-block;
margin-bottom: ${props => props.isShow && '15px'};
max-width: 80px;
height: 30px;
cursor: pointer;
color: #00A0E9;
font-size: 14px;
line-height: 30px;
user-select: none;
&:hover {
color: #37C0FF;
}
`;
const ResponseMessage = styled.div`
margin: 5px 0 0 40px;
`;
const MessageItemWrap = styled.div`
margin-bottom: 15px;
padding: 15px;
border-radius: 5px;
background: #E6E6E6;
width: 500px;
box-sizing: border-box;
p {
margin: 0;
}
`;
```
MessageAtom.js
```javascript=
import React from 'react';
import { connect } from 'react-redux';
import { addMessage } from '../redux/action/message';
import styled from 'styled-components';
import InputBox from '../component/InputBox';
class MessageAtom extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
isShowInputBox: false,
}
this.handleShowInputBox = this.handleShowInputBox.bind(this);
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleShowInputBox() {
const {isShowInputBox} = this.state;
this.setState({isShowInputBox: !isShowInputBox});
}
handleDeleteMessage() {
const {addMessage, messageList, id, isChild} = this.props;
const cloneMessageList = Object.assign([], messageList);
if (isChild) {
cloneMessageList.map((list, index) => {
list.responseArray.find((childList, childIndex) => {
if (id === childList.id) {
cloneMessageList[index].responseArray.splice(childIndex, 1);
addMessage(cloneMessageList);
return childList;
}
});
});
} else {
cloneMessageList.find((list, index) => {
if (id === list.id) {
cloneMessageList.splice(index, 1);
addMessage(cloneMessageList);
return list;
}
});
}
}
render() {
const {onSubmitMessage, id, name, time, value, isChild} = this.props;
const {isShowInputBox} = this.state;
return (
<>
<Info><span>{name}</span> 在 {time} 發佈了這則訊息</Info>
<Message>{value}</Message>
<HandleWrap>
<HandleButton onClick={this.handleShowInputBox}>編輯訊息</HandleButton>
<HandleButton onClick={this.handleDeleteMessage}>刪除訊息</HandleButton>
</HandleWrap>
{
isShowInputBox && <InputBox
isShowInputBox={this.handleShowInputBox}
onSubmitMessage={onSubmitMessage}
id={id}
propsName={name}
propsValue={value}
isChild={isChild}
isReset={false}
/>
}
</>
);
}
}
const mapStateToProps = (state) => ({
messageList: state.messageList,
});
const mapDispatchToProps = (dispatch) => ({
addMessage: (e) => { dispatch(addMessage(e)); },
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(MessageAtom);
const HandleButton = styled.div`
margin-left: 10px;
display: inline-block;
max-width: 80px;
height: 30px;
cursor: pointer;
color: #00A0E9;
font-size: 14px;
line-height: 30px;
user-select: none;
&:hover {
color: #E90000;
}
`;
const HandleWrap = styled.div`
text-align: right;
`;
const Message = styled.pre`
margin: 0;
padding: 10px 0;
color: #303233;
font-size: 16px;
`;
const Info = styled.p`
color: #666;
font-size: 14px;
span {
color: #00A0E9;
font-weight: bold;
}
`;
```
InputBox.js
```javascript=
import React from 'react';
import styled from 'styled-components';
export default class InputBox extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
name: '',
text: '',
};
this.handleTextChange = this.handleTextChange.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleGetTime = this.handleGetTime.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleTextChange(e) {
this.setState({text: e.currentTarget.value});
}
handleNameChange(e) {
this.setState({name: e.currentTarget.value});
}
handleGetTime(fmt) {
const date = new Date();
const o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小時
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
handleSubmit() {
const {isShowInputBox, onSubmitMessage, isReset, isChild, id} = this.props;
const {name, text} = this.state;
if (!text) {
return;
}
onSubmitMessage({
name: name || '匿名人士',
text,
time: this.handleGetTime('yyyy-MM-dd hh:mm:ss'),
responseArray: [],
}, isChild, id);
// isReset,按下送出後需要將name跟text清空,除了編輯訊息
if (isReset) {
this.setState({
name: '',
text: ''
});
}
// 控制編輯訊息的輸入框狀態
isShowInputBox && isShowInputBox();
}
componentDidMount() {
this.setState({
name: this.props.propsName || '',
text: this.props.propsValue || '',
});
}
render() {
const {propsName} = this.props;
const {name, text} = this.state;
return (
<InputBoxWrap>
<Textarea placeholder="在這留下你想說的話" value={text} onChange={this.handleTextChange} />
<Nickname type="text" placeholder="您的大名" value={name} onChange={this.handleNameChange} disabled={propsName} />
<Submit onClick={this.handleSubmit}>確認送出</Submit>
</InputBoxWrap>
);
}
}
InputBox.defaultProps = {
isReset: true,
isChild: false,
};
const Submit = styled.button`
border: none;
border-radius: 5px;
height: 30px;
cursor: pointer;
transition: all .5s ease;
user-select: none;
&:focus {
outline: none;
}
&:hover {
background: #FEBFCF;
}
`;
const Nickname = styled.input`
margin-right: 20px;
border: none;
border-radius: 5px;
padding: 0 10px;
width: 150px;
height: 30px;
box-sizing: border-box;
&:focus {
outline: none;
}
`;
const Textarea = styled.textarea`
border: none;
border-radius: 5px;
padding: 10px;
width: 100%;
height: 200px;
font-size: 20px;
resize: none;
box-sizing: border-box;
&:focus {
outline: none;
}
`;
const InputBoxWrap = styled.div`
margin: 0 auto 20px;
width: 500px;
max-width: 100%;
`;
```