iT邦幫忙

2024 iThome 鐵人賽

DAY 29
0

今天來把 APP 畫面 串接 api,先來講一下流程好了。

  1. 撰寫 API:定義後端的 CRUD API 與前端進行溝通。
  2. 定義 Action:透過 Redux ToolkitcreateAsyncThunk 來處理與後端的非同步請求。
  3. 建立 Slice:將 API 回應的資料透過 Slice 保存到 Redux Store 中,並管理各種狀態。
  4. 串接 UI:將 Redux 中的資料與 React 元件綁定,讓畫面可以顯示並操作資料。

API

在這邊定義昨天的 CRUD api 的路徑與方法

  • createReport:負責建立一筆新的報告資料。
  • fetchAllReports:用來取得所有報告的資料列表。
  • fetchReportById:用於查詢單一報告的詳細資訊。
  • updateReport:負責更新指定報告的內容。

這些方法會通過之前寫的 Axios 來發送 HTTP 請求,並將結果返回給前端使用。

[!NOTE]
API_ENDPOINT 是我們的基礎 API 路徑,並且透過 prefix 統一管理路徑的前綴。

chp555ReportApi.ts

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

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);
    }
  },
);

建立 Slice

接下來我們需要將取得的 API 資料存入到 Redux Store 中,方便在不同頁面可以使用相同資料。

定義 initial State

initialState 中,我們定義了報告列表、單一報告、編輯中的報告 ID、選項資料、請求狀態以及錯誤訊息。這些資料會在我們串接 API 並接收到回應時被更新。

const initialState: Chp555ReportState = {
  reports: [],
  report: {},
  editingReportId: '',
  options: {},
  status: ReduxStateStatus.IDLE,
  error: null,
};

建立 Slice

透過 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;

串接UI

Home page

在首頁,我們提供了一個按鈕,讓使用者可以建立新的報告。當按下 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);
    }
  };
...
}

Report Page

取得報告資料

進入編輯頁面後,我們會根據報告 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鐵人


上一篇
[Day28] Chp555 報告 next.js CRUD 實作
下一篇
[Day30] 完賽感想
系列文
30天 使用chatGPT輔助學習APP完成接案任務委託30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言