本文為實作 W24 作業二 SPA blog 加強版的過程,主要紀錄解題心得跟 error logs
把 W22 做的 react 部落格增加或改寫以下功能:
增加刪除文章、編輯文章功能
把 user 存在 redux,不要用 context
用 redux-thunk 發 API,loading 狀態以及 response 都會存在 store 裡面。
1. 刪除功能:
a. WebAPI
export const deleteArticle = (id) => {
const token = getAuthToken();
return fetch(`${BASE_URL}/posts/${id}`, {
method: "DELETE",
headers: {
"content-type": "application/json",
authorization: `Bearer ${token}`,
},
}).then((res) => res.json());
};
b. reducer
這裡沒有設狀態,只是單純呼叫 API 且回傳一個 promise
export const deletePost = (id) => () => {
return deleteArticle(id).then((res) => {
return res;
});
};
c. 在 ArticlePage 頁面
c.1 刻刪除按鈕
c.2 監聽事件,把 props 傳下去
function Article({ article, handleDeleteClick }) {
return (
<ArticleWrapper>
<DeleteArticle onClick={handleDeleteClick}>Delete</DeleteArticle>
<ArticleTitle>{article.title}</ArticleTitle>
<ArticleContent>{article.body}</ArticleContent>
</ArticleWrapper>
);
}
c.3 呼叫刪除文章的函式,刪完導回首頁
export default function ArticlePage() {
let { id } = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
const article = useSelector((store) => store.posts.post);
useEffect(() => {
dispatch(getPost(id));
}, [id, dispatch]);
const handleDeleteArticle = () => {
dispatch(deletePost(id)).then(() => navigate('/'));
}
return <Article article={article} handleDeleteClick={handleDeleteArticle}/>;
}
小結:回傳一個 promise 這件事不太直覺
2. 編輯文章功能
a. WebAPI
export const editArticle = (id, title, body) => {
const token = getAuthToken();
return fetch(`${BASE_URL}/posts/${id}`, {
method: "PATCH",
headers: {
"content-type": "application/json",
authorization: `Bearer ${token}`,
},
body: JSON.stringify({
title,
body,
}),
}).then((res) => res.json());
};
b. reducer
export const editPost = (id, title, body) => (dispatch) => {
dispatch(setIsLoadingPost(true));
editArticle(id, title, body).then(res => {
dispatch(setPost(res));
dispatch(setIsLoadingPost(false));
})
}
c. 設置編輯頁面並引入 router
import EditPage from "../../Pages/EditPage";
<Route path="/edit/:id" element={<EditPage />} />
d. 新增一個 editPage,改寫自新增文章頁面
import { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import styled from "styled-components";
import { editPost } from '../../redux/reducers/postReducer';
import { useDispatch, useSelector } from "react-redux";
const ArticleWrapper = styled.form`
margin: 0 auto;
width: 800px;
padding: 20px;
`;
const ArticleTitle = styled.input`
width: 360px;
margin: 20px;
`;
const ArticleTextArea = styled.textarea`
width: 600px;
height: 300px;
border-radius: 3px;
`;
const ArticleSubmit = styled.button`
color: #666;
display: block;
margin-top: 20px;
`;
const ErrorMessage = styled.div`
color: red;
font-size: 20px;
margin-bottom: 20px;
`;
export default function EditPage() {
let { id } = useParams();
const navigate = useNavigate();
const article = useSelector((store) => store.posts.post);
const dispatch = useDispatch();
const [title, setTitle] = useState(article.title);
const [body, setBody] = useState(article.body);
const [errorMessage, setErrorMessage] = useState("");
const handleEditArticle = () => {
setErrorMessage(null);
if (!title || !body) {
const errorMessage = "Please input missing field";
setErrorMessage(errorMessage);
} else {
dispatch(editPost(id, title, body))
navigate(`/posts/${id}`);
}
};
return (
<ArticleWrapper onSubmit={handleEditArticle}>
Title:
<ArticleTitle
value={title}
onChange={(e) => setTitle(e.target.value)}
type="text"
/>
<ArticleTextArea
value={body}
onChange={(e) => setBody(e.target.value)}
type="text"
/>
{errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>}
<ArticleSubmit>Save</ArticleSubmit>
</ArticleWrapper>
);
}
3. 把 user 從 context 改成存在 redux 裡面
問題描述:登入跳轉首頁,沒有抓到 user,也沒有顯示 new-post,要重新整理才抓得到 user
- 原來是 header 忘了改啦
error log:
react_devtools_backend.js:4045 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at LimitArticles (http://localhost:3000/static/js/bundle.js:913:76)
如果沒有加上 return undefined;
就會出現上面的訊息
export const loginBlog = (username, password) => (dispatch) => login(username, password)
.then((resLogin) => {
if (resLogin.ok === 0) {
dispatch(setErrorMessage(resLogin.message));
// return undefined;
}
setAuthToken(resLogin.token);
return getMe().then((resMe) => {
if (resMe.ok !== 1) {
setAuthToken(null);
dispatch(setErrorMessage(resMe.toString()));
return undefined;
}
dispatch(setUser(resMe.data));
return resMe;
})
})
error log: Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
因為 WebAPI 裡面的 title, body 沒有解構
signup 跳轉頁面問題
- 無法註冊成功以後跳轉到首頁,最後先用了
e.preventDefault();
,阻止事件送出,接著在 reducer 那裡把 navigate 傳進去,並且轉向首頁;在 signup 頁面引入useNavigate()
加上dispatch(signUpBlog(navigate, nickname, username, password))
就可以了
- 無法註冊成功以後跳轉到首頁,最後先用了
不同使用者編輯刪除權限的功能:對比文章與使用者 id,只有此文章是該使用者發布的才能編輯
抓不到總頁數:重點在於定義問題,錯誤訊息看多了,固然會知道怎麼去解,但是問題問錯了,方式會永遠找不到。
例如說,我找不到總文章數,而我知道老師有說過,他會在 response 的 headers 裡面的一個叫做 x-total-count 這個裡面,但問題是,當我整個程式碼不方便拿這個資料時,我有沒有其他方式可以找到「文章總數」呢?其實就是整個文章的長度就是總數了。
轉個彎也很好。