Skip to content

Instantly share code, notes, and snippets.

@u88803494
Last active February 20, 2020 14:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save u88803494/c15b611b0d76c3f46ae43181acc7ef74 to your computer and use it in GitHub Desktop.
Save u88803494/c15b611b0d76c3f46ae43181acc7ef74 to your computer and use it in GitHub Desktop.
串 API 跟同步畫面,還有實作偵測功能
.form__datatype {
display: flex;
justify-content: space-between;
}
.form__empty {
color: red;
}
.form__empty--submit {
display: flex;
justify-content: flex-end;
}
import React, { useState, useEffect } from 'react';
import { Button, Modal, Form } from 'react-bootstrap';
import './editing_window.css';
import * as webAPI from '../WebAPI';
const EditingWindow = ({ onHide, show, post, status, handleChangePosts }) => {
const newPost = { title: '', author: '', body: '', };
const defaultEmpty = { title: false, author: false, body: false, };
const defaultSubmitType = { canSubmit: true, status: '', };
const [thisPost, setThisPost] = useState(post ? post : newPost);
const [isEmpty, setEmpty] = useState(defaultEmpty); // 為了一開始不偵測
const [submitType, setSubmitType] = useState(defaultSubmitType);
// 一開始先不偵測,因為本身載入的都是有內容的。
const changePost = (e) => {
if (!e.target.value) { // 輸入時確認是否為空
setEmpty({ ...isEmpty, [e.target.name]: true, })
} else {
setEmpty({ ...isEmpty, [e.target.name]: false, })
}
setThisPost({ ...thisPost, [e.target.name]: e.target.value, })
}
const handleSubmit = () => {
if (!thisPost.title || !thisPost.author || !thisPost.body) {
setSubmitType({ canSubmit: false, status: '資料不全,無法送出,繼續完成資料才可送出', });
return;
}
const whichAPI = (thisPost, status) => status === 'create' ?
webAPI.createPost(thisPost) : webAPI.updatePost(thisPost)
const submitPost = (status, thisPost) => {
handleChangePosts(status, thisPost); // 改變畫面上的資料
onHide();
}
const onError = (err) => {
setSubmitType({ canSubmit: false, status: `發生問題無法送出 ${err}`, });
}
whichAPI(thisPost, status)
.then(res => res.status <= 300 && submitPost(status, thisPost))
.catch(err => onError(err))
}
useEffect(() => {
if (thisPost.title && thisPost.author && thisPost.body) {
setSubmitType({ canSubmit: true, status: '', });
} // render 檢測值是否為空
}, [thisPost])
return (
<Modal
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
{...{ onHide, show }}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
{status === "editing" ? "你正在編輯文章" : "你正在新增文章"}
</Modal.Title>
</Modal.Header>
<Form>
<Modal.Body>
<Form.Group>
<div className="form__datatype">
<Form.Label>標題</Form.Label>
<Form.Text className="form__empty">
{isEmpty.title && '標題不能為空'}
</Form.Text>
</div>
<Form.Control
name="title"
type="text"
placeholder="Enter title"
value={thisPost && thisPost.title}
onChange={changePost}
/>
</Form.Group>
<Form.Group>
<div className="form__datatype">
<Form.Label>作者</Form.Label>
<Form.Text className="form__empty">
{isEmpty.author && '作者不能為空'}
</Form.Text>
</div>
<Form.Control
name="author"
type="text"
placeholder="author/作者"
value={thisPost && thisPost.author}
onChange={changePost}
/>
</Form.Group>
<Form.Group>
<div className="form__datatype">
<Form.Label>內文</Form.Label>
<Form.Text className="form__empty">
{isEmpty.body && '內容不能為空'}
</Form.Text>
</div>
<Form.Control
name="body"
as="textarea"
rows="5"
placeholder="輸入內文"
value={thisPost && thisPost.body}
onChange={changePost}
/>
</Form.Group>
<Form.Text className="form__empty form__empty--submit">
{submitType.status}
</Form.Text>
</Modal.Body>
<Modal.Footer>
<Button
variant="outline-secondary"
onClick={onHide}
>
Close
</Button>
<Button
variant="outline-primary"
onClick={handleSubmit}
disabled={!submitType.canSubmit}
>
{status === 'editing' ? '儲存文章' : '新增文章'}
</Button>
</Modal.Footer>
</Form>
</Modal>
);
}
const DeleteWindow = ({ onHide, show, post, status, handleChangePosts }) => {
const [loadingState, setLoadingState] = useState('是的,我要刪除');
useEffect(() => {
const finalExecution = (success) => { // 根據成功與否改變按鈕的內容
success ? setLoadingState('刪除成功!') : setLoadingState('刪除失敗!')
setTimeout(() => {
success ? handleChangePosts(status, post) : setLoadingState('是的,我要刪除')
}, 1000)
} /** 放內部就不用使用 useCallback */
if (loadingState === '刪除中........') {
webAPI.deletePost(post.id) // 改變伺服器
.then(res => res.status < 300 && finalExecution(true) /* 改變父狀態 */)
.catch(() => finalExecution(false))
}
}, [loadingState, handleChangePosts, post, status]);
const handleDelete = () => {
setLoadingState('刪除中........')
}
return (
<Modal
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
{...{ onHide, show }}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
警告!你正在刪除文章
</Modal.Title>
</Modal.Header>
<Modal.Body>
你確定要刪除文章嗎?
</Modal.Body>
<Modal.Footer>
<Button variant="outline-secondary" onClick={onHide}>
不了,我不要刪除
</Button>
<Button
variant="outline-danger"
onClick={handleDelete}
disabled={loadingState !== '是的,我要刪除'}
>
{loadingState}
</Button>
</Modal.Footer>
</Modal>
);
}
export { EditingWindow, DeleteWindow };
import React, { Component, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { ListGroup, Button, Spinner } from 'react-bootstrap';
import './post_list.css';
import { EditingWindow, DeleteWindow } from '../editing_window/';
import { getPosts } from '../WebAPI';
const ControllerButton = ({ post, handleChangePosts }) => {
const [editingShow, setEditingShow] = useState(false);
const [deleteShow, setDeleteShow] = useState(false);
const handleEdit = () => setEditingShow(true); // 另外寫出來省資源
const handleDelete = () => setDeleteShow(true);
return (
<div className="blog__controller">
<Button variant="outline-success" onClick={handleEdit} >編輯</Button>
{
editingShow &&
<EditingWindow /** 編輯視窗 */
show={editingShow}
onHide={() => setEditingShow(false)}
status="editing"
post={post}
handleChangePosts={handleChangePosts}
/>
}
<Button variant="outline-danger" onClick={handleDelete}>刪除</Button>
{
deleteShow &&
<DeleteWindow
show={deleteShow}
onHide={() => setDeleteShow(false)}
status="delete"
post={post}
handleChangePosts={handleChangePosts}
/>
}
</div>
);
};
const RenderPosts = ({ data, history, handleChangePosts }) => (
<>
{
data.map(post => (
<ListGroup.Item
key={post.id}
className="blog__post"
>
<div
className="blog__title"
onClick={() => history.push("/posts/" + post.id)}
>
{post.title}
</div>
<ControllerButton handleChangePosts={handleChangePosts} post={post} />
</ListGroup.Item>
))
}
</>
)
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isCreate: false,
}
this.id = 1;
}
handleCreate = (isCreate) => {
this.setState({ isCreate, })
}
handleChangePosts = (method, changeData) => {
/** 第一個變數是方式,第二個變更的資料 */
const { data } = this.state;
switch (method) {
case 'create':
this.setState({
data: [{
...changeData,
createdAt: new Date().getTime(), // 取得當前的 timestamp,雖然應該會跟伺服器上的不同
id: this.id,
},
...data, // 放後面才能符合逆排序
],
})
this.id += 1;
break;
case 'editing':
this.setState({
data: data.map((post) => {
if (post.id !== changeData.id) return post;
return {
...post,
...changeData,
};
})
});
break;
case 'delete':
this.setState({
data: data.filter(post => post.id !== changeData.id)
})
break;
default:
console.log('一定是搞錯了什麼');
}
}
componentDidMount() {
getPosts() // call api 也許可以改在 RenderPosts 那裡
.then(res => {
this.setState({
data: res.data
.filter(({ title, author, body }) => title && author && body)
.sort((a, b) => b.id - a.id),
}); // 太多無用資料,決定先篩選之後逆排序
this.id = res.data.length !== 0 ? res.data[res.data.length - 1].id + 1 : 1;
});
}
render() { /** 之後可以改成兩種呈現方式,條列式格狀顯示 */
const { data, isCreate } = this.state;
const { history } = this.props;
return (
<div className="blog">
<header className="header">
<div className="header__title">部落格文章</div>
<div className="header__newpost">
<Button
variant="outline-primary"
onClick={() => this.handleCreate(true)}
>
新增文章
</Button>
{
isCreate &&
<EditingWindow /* 新增共用編輯視窗 */
show={isCreate}
onHide={() => this.handleCreate(false)}
status="create"
handleChangePosts={this.handleChangePosts}
/>
}
</div>
</header>
<main className="blog__posts">
{/** 判斷是否讀取中 */
data.length ?
<RenderPosts
data={data}
history={history}
handleChangePosts={this.handleChangePosts}
/> :
<Spinner animation="border" />
}
</main>
</div>
)
}
}
export default withRouter(Posts);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment