Redux SPA blog 加強版


Posted by Christy on 2022-01-07

本文為實作 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 這個裡面,但問題是,當我整個程式碼不方便拿這個資料時,我有沒有其他方式可以找到「文章總數」呢?其實就是整個文章的長度就是總數了。

    • 轉個彎也很好。










Related Posts

跟爺爺奶奶們度過開心快樂的夏令營

跟爺爺奶奶們度過開心快樂的夏令營

Vue3使用pinia之用法

Vue3使用pinia之用法

Linkedin Java 技術認證題庫

Linkedin Java 技術認證題庫


Comments