建立 Constants
在 constants
資料夾中建立對應 userConstants.js
,並寫好對應的 constants
:
1 2 3 4 5
| ..................... export const USER_UPDATE_PROFILE_REQUEST = "USER_UPDATE_PROFILE_REQUEST"; export const USER_UPDATE_PROFILE_SUCCESS = "USER_UPDATE_PROFILE_SUCCESS"; export const USER_UPDATE_PROFILE_FAIL = "USER_UPDATE_PROFILE_FAIL"; export const USER_UPDATE_PROFILE_RESET = "USER_UPDATE_PROFILE_RESET";
|
撰寫 Reducer
在 reducers
資料夾中建立對應 userReducers.js
,並撰寫對應的 reducer
,並將剛剛寫好的 constants
import 進來:
1 2 3 4 5 6 7
| import { ........... USER_UPDATE_PROFILE_REQUEST, USER_UPDATE_PROFILE_SUCCESS, USER_UPDATE_PROFILE_FAIL, USER_UPDATE_PROFILE_RESET, } from "../constants/userConstants.js";
|
接著撰寫對應的 reducer
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export const userUpdateProfileReducer = (state = {}, action) => { switch (action.type) { case USER_UPDATE_PROFILE_REQUEST: return { loading: true }; case USER_UPDATE_PROFILE_SUCCESS: return { loading: false, success: true, userInfo: action.payload }; case USER_UPDATE_PROFILE_FAIL: return { loading: false, error: action.payload }; case USER_UPDATE_PROFILE_RESET: return {}; default: return state; } };
|
- 在 case
USER_UPDATE_PROFILE_SUCCESS
多一個 success 物件是為了到時候在 ProfilePage.js output 判斷是否成功更新 user profile,並顯示對應的訊息
- 而
USER_UPDATE_PROFILE_RESET
這邊是為了在 ProfilePage.js 中的 useEffect 中,當 user 更新成功後就清空 state 中 userUpdateProfile
的資料
在 store.js 中加入 reducer
一樣將剛剛寫好的 reducer
import 進來,並將其加入 store
常數中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { ............ userUpdateProfileReducer, } from "./reducers/userReducers"; ..................... const store = configureStore({ reducer: { ........................... userDetails: userDetailsReducer, userUpdateProfile: userUpdateProfileReducer, }, preloadedState: initialState, middleware: middleware, });
|
建立對應 Action
在 actions
資料夾中建立對應 userActions.js
,並撰寫對應的 action
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| export const updateUserProfile = (user) => async (dispatch, getState) => { try { dispatch({ type: USER_UPDATE_PROFILE_REQUEST, });
const { userLogin: { userInfo }, } = getState();
const config = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${userInfo.token}`, }, }; const { data } = await axios.put( `/api/users/profile/update/`, user, config );
dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: data, }); dispatch({ type: USER_LOGIN_SUCCESS, payload: data, });
localStorage.setItem("userInfo", JSON.stringify(data)); } catch (error) { dispatch({ type: USER_UPDATE_PROFILE_FAIL, payload: error.response && error.response.data.message ? error.response.data.message : error.message, }); } };
|
更新完後應該要讓用戶在自動登入,所以要才要再 dispatch USER_LOGIN_SUCCESS
以及更新 localStorage
中的 userInfo
。
在 ProfilePage.js 中使用
在 ProfilePage.js
中,將剛剛寫好的 action
import 進來,並用 useDispatch
取得 dispatch
, useSelector
取得對應的 state
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { getUserDetails, updateUserProfile } from "../actions/userActions"; .......................... const dispatch = useDispatch();
const userDetails = useSelector((state) => state.userDetails); const { loading, error, user } = userDetails;
const userLogin = useSelector((state) => state.userLogin); const { userInfo } = userLogin;
const userUpdateProfile = useSelector((state) => state.userUpdateProfile); const { success } = userUpdateProfile;
|
然後用 useEffect
去判斷是否有登入,沒有登入就跳轉到登入頁面,有登入就去取得 user 的資料,
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| useEffect(() => { if (!userInfo) { navigate("/login"); } else { if (!user || !user.first_name || success) { dispatch({ type: USER_UPDATE_PROFILE_RESET }); dispatch(getUserDetails("profile")); } else { setFirst_name(user.first_name); setEmail(user.email); } } }, [dispatch, navigate, userInfo, user, success]);
|
在 ProfilePage.js 中加入更新 user profile 的表單
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <Form onSubmit={submitHandler}> <Form.Group controlId="first_name"> <Form.Label>ID</Form.Label> <Form.Control required type="name" placeholder="請輸入您的ID" value={first_name} onChange={(e) => setFirst_name(e.target.value)} ></Form.Control> </Form.Group> ................................... <Button type="submit" variant="primary"> 更新 </Button> </Form>
|
submitHandler 事件處理函數
setMessage
是用來設定顯示在表單最上方的訊息,若密碼不一樣就會顯示錯誤訊息,若沒問題就會 dispatch updateUserProfile
這個 action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const submitHandler = (e) => { e.preventDefault();
if (password !== confirmPassword) { setMessage({ text: "密碼不一樣啦!!", variant: "danger" }); } else { dispatch( updateUserProfile({ id: user.id, first_name: first_name, email: email, password: password, }) ); setMessage({ text: "更新成功!", variant: "success" }); } };
|
表單檢查沒問題後,就把更新後的資料回傳給 updateUserProfile
這個 action 的 user 參數。
成功畫面
最後應可以直接看到成功更新後的資料顯示在原本的欄位上,不需要重整畫面,且在 Redux DevTools 中也能看到 USER_UPDATE_PROFILE_SUCCESS
的 action 有被觸發,而 USER_DETAILS_REQUEST
也有被觸發,因為在更新成功後,會再去取得 user 的資料,並更新到 userDetails
的 state 中。
在 localStorage 中也能看到更新後的 userInfo
常見問題
更新後成功觸發 dispatch
,卻出現 401 (Unauthorized)
錯誤
這個往往是因為請求缺乏必要的授權憑證
,大部分都是因為 token
沒有帶上的問題。
檢查步驟:
- 對應的
action
中,檢查是否有帶上 token
。
- 檢查後端 Django 處理更新會員資料的
views.py
看看,是否有設定 permission_classes
- 檢查後端 Django 的
serializers.py
中,是否在將 UserSerializerWithToken
函示中,存取正確的token
,應該要 return access_token
,而不是 refresh_token
。
1 2 3
| def get_token(self,obj): token = RefreshToken.for_user(obj) return str(token.access_token)
|
要記得要用調用對的 serializer
,不然會出現 token
不存在的錯誤。例如這裡要用 UserSerializerWithToken
,而不是 UserSerializer
。
1 2 3 4 5 6 7 8
| @api_view(['PUT']) @permission_classes([IsAuthenticated]) def updateUserProfile(request): user = request.user serializer = UserSerializerWithToken(user,many=False) ..................... user.save() return Response(serializer.data)
|
若您覺得這篇文章對您有幫助,歡迎分享出去讓更多人看到⊂◉‿◉つ~
留言版