今天來把 APP 畫面 串接 api,先來講一下流程好了。
Redux Toolkit
的 createAsyncThunk
來處理與後端的非同步請求。Slice
保存到 Redux Store 中,並管理各種狀態。在這邊定義昨天的 CRUD api 的路徑與方法
這些方法會通過之前寫的 Axios 來發送 HTTP 請求,並將結果返回給前端使用。
[!NOTE]
API_ENDPOINT
是我們的基礎 API 路徑,並且透過prefix
統一管理路徑的前綴。
import api from '../lib/configAxios';
import {API_ENDPOINT} from '../lib/configAxios';
import {ICreateProps, ICreateReportResponse} from '../types/chp555Report';
const prefix = 'chp555-report';
const chp555ReportApi = {
createReport(data: ICreateProps) {
return api.post<ICreateReportResponse>(`${API_ENDPOINT}/${prefix}`, data);
},
fetchAllReports() {
return api.get(`${API_ENDPOINT}/${prefix}`);
},
fetchReportById(reportId: string) {
return api.get(`${API_ENDPOINT}/${prefix}/${reportId}`);
},
updateReport(data: any) {
return api.patch(`${API_ENDPOINT}/${prefix}/report`, data);
},
};
export default chp555ReportApi;
Action
是我們與後端 API 溝通的中介,負責將 API 的結果轉化為 Redux 狀態變更的一部分。
在 createChp555Report
這個 action 中,我們傳入 data
,這個 data
是用戶的資料,這邊設計會回傳 reportId 在後續的操作中使用。
export const createChp555Report = createAsyncThunk(
CHP555_REPORT.CREART_CHP555_REPORT,
async (data: ICreateProps, thunkAPI) => {
try {
const response = await chp555ReportApi.createReport(data);
return response.data;
} catch (error: unknown) {
if (error instanceof Error) {
return thunkAPI.rejectWithValue(error.message);
}
return thunkAPI.rejectWithValue('未知錯誤');
}
},
);
這裡的 fetchAllReports
則是負責調用 API 並將所有報告資料保存到 Redux 中,供後續的畫面顯示使用。
export const fetchAllReports = createAsyncThunk(
CHP555_REPORT.FETCH_ALL_REPORTS,
async (_, thunkAPI) => {
try {
const response = await chp555ReportApi.fetchAllReports();
return response.data;
} catch (error: unknown) {
const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
return thunkAPI.rejectWithValue(errorMessage);
}
},
);
updateReport
則是接收要更新的資料並發送到後端。當更新成功後,Redux 中的狀態會被更新,前端畫面也會同步反映最新的資料。
export const updateReport = createAsyncThunk(
CHP555_REPORT.UPDATE_REPORT,
async (data: any, thunkAPI) => {
try {
const response = await chp555ReportApi.updateReport(data);
return response.data;
} catch (error: unknown) {
const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
return thunkAPI.rejectWithValue(errorMessage);
}
},
);
透過 fetchReportById
,我們可以根據報告的 ID 來取得特定報告的詳細資料,這在進入編輯頁面時非常有用。
export const fetchReportById = createAsyncThunk(
CHP555_REPORT.FETCH_REPORT_BY_ID,
async (reportId: string, thunkAPI) => {
try {
const response = await chp555ReportApi.fetchReportById(reportId);
return response.data;
} catch (error: unknown) {
const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
return thunkAPI.rejectWithValue(errorMessage);
}
},
);
接下來我們需要將取得的 API 資料存入到 Redux Store 中,方便在不同頁面可以使用相同資料。
在 initialState
中,我們定義了報告列表、單一報告、編輯中的報告 ID、選項資料、請求狀態以及錯誤訊息。這些資料會在我們串接 API 並接收到回應時被更新。
const initialState: Chp555ReportState = {
reports: [],
report: {},
editingReportId: '',
options: {},
status: ReduxStateStatus.IDLE,
error: null,
};
透過 createSlice
,我們將各個 Action
的結果處理邏輯統一管理起來。每個 action 都會有 pending、fulfilled 和 rejected 狀態 (這邊我們另外寫types定義),分別對應到請求進行中、成功和失敗的狀況。這裡我們還會更新 status
,讓 UI 可以根據不同狀態來顯示 Loading 畫面或錯誤提示。
const chp555ReportSlice = createSlice({
name: 'chp555Reports',
initialState,
reducers: {},
extraReducers: builder => {
builder
.addCase(createChp555Report.pending, state => {
state.status = ReduxStateStatus.LOADING;
})
.addCase(createChp555Report.fulfilled, (state, action) => {
state.status = ReduxStateStatus.SUCCEEDED;
state.editingReportId = action.payload.reportId;
})
.addCase(createChp555Report.rejected, (state, action: any) => {
state.status = ReduxStateStatus.FAILED;
state.error = action.payload.message;
})
.addCase(fetchAllReports.pending, state => {
state.status = ReduxStateStatus.LOADING;
})
.addCase(fetchAllReports.fulfilled, (state, action) => {
state.status = ReduxStateStatus.SUCCEEDED;
state.reports = action.payload;
})
.addCase(fetchReportOptions.pending, state => {
state.status = ReduxStateStatus.LOADING;
})
.addCase(fetchReportOptions.fulfilled, (state, action) => {
state.status = ReduxStateStatus.SUCCEEDED;
state.options = action.payload;
})
.addCase(updateReport.pending, state => {
state.status = ReduxStateStatus.LOADING;
})
.addCase(updateReport.fulfilled, (state, _action) => {
state.status = ReduxStateStatus.SUCCEEDED;
});
},
});
export default chp555ReportSlice.reducer;
在首頁,我們提供了一個按鈕,讓使用者可以建立新的報告。當按下 Create Chp555Report 按鈕時,我們會觸發 createChp555Report
action,並在成功後導向報告編輯頁面。這裡使用了 dispatch
方法來呼叫 action,並且利用 navigation
來進行頁面跳轉。
import {useDispatch, useSelector} from 'react-redux';
...
const HomePage: React.FC<HomePageProps> = ({navigation}) => {
const dispatch = useDispatch();
...
const handleCreateReport = async () => {
try {
await dispatch(createChp555Report({userId, username})).then(() => {
navigation.navigate(RouteNames.ReportPage);
});
} catch (error) {
console.error('Error creating report:', error);
}
};
...
}
進入編輯頁面後,我們會根據報告 ID 來自動取得對應的報告資料。這裡我們設置了一個 loading
狀態,當資料還沒取得時,畫面會顯示 loading,避免使用者看到不完整的畫面。
...
const [isLoading, setIsLoading] = useState(true);
...
useEffect(() => {
if (reportId) {
dispatch(fetchReportById({id: reportId}))
.then(unwrapResult)
.then(() => setIsLoading(false))
.catch(error => {
console.error('Failed to fetch report:', error);
setIsLoading(false);
});
} else {
setIsLoading(false);
}
}, [reportId]);
當使用者修改資料並提交後,我們會使用 updateReport
action 來更新資料,並且在成功或失敗後,分別顯示對應的通知訊息。
const handleSubmit = async () => {
const data = {
...formState,
id: reportId,
};
try {
await dispatch(updateReport(data)).then(unwrapResult);
toast.show({
title: 'Success',
description: 'Report updated successfully!',
placement: 'top',
backgroundColor: 'green.500',
});
} catch (error: any) {
toast.show({
title: 'Error',
description: `Failed to update report: ${error.message}`,
placement: 'top',
backgroundColor: 'red.500',
});
}
};
在報告列表頁面,我們會透過 fetchAllReports
取得所有的報告資料,並將其渲染到頁面上。每個報告都會有一個編輯按鈕,點擊後會跳轉到對應的報告編輯頁面,這樣使用者可以查看或修改該報告的詳細內容。
...
useEffect(() => {
dispatch(fetchAllReports());
}, [dispatch]);
// 取得所有报告
const reports = useSelector(state => state.chp555Reports.reports);
// 跳轉編輯
const handleEdit = id => {
navigation.navigate(RouteNames.ReportPage, {editReportId: id});
console.log(`Edit item with id: ${id}`);
};
return (
....
{reports.map((item, index) => (
.....)}
...
};
export default SimpleTable;
我們把 api 串接到畫面了,剩下的部分就是完善表單欄位的處理,這些細節將會是下一步的工作。
#it鐵人