首页 > 解决方案 > 使用 useReducer 手动获取数据

问题描述

我不仅需要在 ComponentDidMount 上使用,loadData()还需要在 methodsonFiltersClear和. 我怎么能那样做?我需要一些自定义数据获取钩子还是有其他方法可以做到这一点?onApplyFiltersonPageChange

import { Table } from "lpcnse-core-front";
import Filters from "./Filters/Filters";
import { styles } from "./checks.css";

const STATUSES_PARENT_ID = "2F24B71C-C70A-4980-9D45-E4134D38E9E0";

const ITEMS_PER_PAGE = 20;

const INITIAL_PAGINATION = {
  pageNumber: 1,
  itemsPerPageCount: ITEMS_PER_PAGE,
  totalCount: 0,
  pageCount: 0
};

const INITIAL_FILTERS = {
  title: "",
  сontrolledObject: "",
  status: null
};

const initialState = {
  filters: INITIAL_FILTERS,
  checks: [],
  pagination: INITIAL_PAGINATION,
  selectedCheckId: null,
  statusList: []
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_DATA_PENDING":
      return {
        ...state,
        checks: []
      };
    case "FETCH_DATA_SUCCESS":
      const { items, pageNumber, pageCount, totalCount } = action.payload;
      return {
        ...state,
        checks: items,
        pagination: {
          ...state.pagination,
          pageCount,
          totalCount,
          pageNumber
        }
      };
    case "FETCH_DATA_ERROR":
      return {
        ...state,
        checks: [],
        pagination: INITIAL_PAGINATION
      };
    case "SET_FILTER_INPUT":
      return {
        ...state,
        filters: {
          ...state.filters,
          ...action.payload
        }
      };
    case "SET_FILTER_STATE":
      const { status } = action.payload;
      return {
        ...state,
        filters: {
          ...state.filters,
          status
        }
      };
    case "CLEAR_FILTERS":
      return {
        ...state,
        filters: INITIAL_FILTERS
      };
    case "SET_PAGE_NUMBER":
      return {
        ...state,
        pagination: {
          ...state.pagination,
          pageNumber
        }
      };
    case "RESET_PAGINATION":
      return {
        ...state,
        pagination: INITIAL_PAGINATION
      };
    case "FETCH_STATUSES_SUCCESS":
      const { statusList } = action.payload;
      return {
        ...state,
        statusList
      };
    case "FETCH_STATUSES_ERROR":
      return {
        ...state,
        statusList: []
      };
    default:
      throw new Error();
  }
};

export const Checks = props => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const {
    checks,
    pagination: { pageCount, pageNumber },
    filters,
    filters: { title, сontrolledObject, status },
    selectedCheckId,
    statusList
  } = state;

  useEffect(() => {
    loadData();
    loadStatusList();
  }, []);

  const loadData = async () => {
    try {
      const response = await props.api.check.getChecks({
        filters: {
          title,
          сontrolledObject,
          status
        },
        pageNumber,
        maxRowCount: ITEMS_PER_PAGE
      });

      const { items, pageNumber, pageCount, totalCount } = response.data;
      dispatch({
        type: "FETCH_DATA_SUCCESS",
        payload: {
          pageNumber,
          pageCount,
          totalCount,
          items: items || []
        }
      });
    } catch (error) {
      dispatch({
        type: "FETCH_DATA_ERROR"
      });
    }
  };

  const loadStatusList = async () => {
    try {
      const response = await props.api.type.getStatuses({
        parentId: STATUSES_PARENT_ID
      });
      const { items } = response.data;
      dispatch({
        type: "FETCH_STATUSES_SUCCESS",
        payload: {
          statusList: items || []
        }
      });
    } catch (error) {
      dispatch({
        type: "FETCH_STATUSES_ERROR"
      });
    }
  };

  const onFilterInputChange = e => {
    dispatch({
      type: "SET_FILTER_INPUT",
      payload: {
        [e.target.name]: e.target.value
      }
    });
  };

  const onFilterStatusChange = e => {
    dispatch({
      type: "SET_FILTER_STATE",
      payload: {
        status
      }
    });
  };

  const onFiltersClear = () => {
    dispatch({
      type: "CLEAR_FILTERS"
    });

    // loadData
  };

  const onApplyFilters = () => {
    dispatch({
      type: "RESET_PAGINATION"
    });

    // loadData
  };

  const onPageChange = data => {
    dispatch({
      type: "SET_PAGE_NUMBER",
      payload: {
        pageNumber: data.selected + 1
      }
    });

    // loadData
  };

  const onSort = () => {};

  const onOpenUserCard = () => {};

  const columns = [
    //some columns
  ];

  return (
    <div className="checks">
      <Filters
        onFilterInputChange={onFilterInputChange}
        onFilterStatusChange={onFilterStatusChange}
        onApplyFilters={onApplyFilters}
        onClear={onFiltersClear}
        filters={filters}
        statusList={statusList}
      />
      <Table
        columns={columns}
        data={checks}
        onSort={onSort}
        paginated={true}
        onPageChange={onPageChange}
        pageCount={pageCount}
        forcePage={pageNumber}
        selectedItemId={selectedCheckId}
      />

      <style jsx>{styles}</style>
    </div>
  );
};

标签: reactjsreact-hooks

解决方案


使加载函数对过滤器更改做出反应的关键是将过滤器添加到useEffect()依赖项列表中。

useEffect(() => {
  loadData();
  loadStatusList();
}, [filters, pageNumber]);

虽然我没有看到loadStatusList()依赖于过滤器,所以建议将两个异步调用分成它们自己的效果

useEffect(() => {
  loadData();
}, [filters, pageNumber]);

useEffect(() => {
  loadStatusList();
}, []);

最后,在每次渲染时都会重新创建 load* 函数,因此您可以将它们移动到各自的效果中,或者将它们包装在 中useCallback(),或者将它们移到组件之外(但是您必须传递所有参数而不是使用闭包)。

useEffect(() => {

  const loadData = async () => {
    ...
  });

  loadData();
}, [filters]);

useEffect(() => {

  const loadStatusList = async () => {
    ...
  });

  loadStatusList();
}, []);

我可以从评论中看到您没有连接事件(按钮单击)并且状态发生变化。

  1. 副作用,尤其是异步的,必须包含在useEffect().

  2. useEffects在状态更改时触发(或 === onMount 的空依赖列表的特殊情况)。

  3. 由于您只能对状态更改运行效果,因此您必须使事件(按钮单击)更改某些状态,然后在useEffect()依赖项列表中使用该状态作为触发器。

因此,在 onFilterInputChange 事件之后

onFilterInputChange 
-> dispatch(SET_FILTER_INPUT) 
-> reducer sets new value for filters
-> effect has filters as dependency, so it runs
-> loadData called with new filters as parameter
-> on response dispatch(FETCH_DATA_SUCCESS) with new data

我们可能遇到的唯一问题pagerNumber是效果的输入和输出,因此在更新时可能会出现反馈循环。

为了安全起见,我们可以添加一个控制获取的状态变量

const initialState = {
  filters: INITIAL_FILTERS,
  checks: [],
  pagination: INITIAL_PAGINATION,
  selectedCheckId: null,
  statusList: [],
  doFetch: true    // initially true === do a fetch on mount
};

const reducer = (state, action) => {
  switch (action.type) {

    case "FETCH_DATA_PENDING":
      return {
        ...state,
        checks: [],
        doFetch: true
      };    

    case "FETCH_DATA_SUCCESS":
      const { items, pageNumber, pageCount, totalCount } = action.payload;
      return {
        ...state,
        checks: items,
        pagination: {
          ...state.pagination,
          pageCount,
          totalCount,
          pageNumber
        },
        doFetch: false
      };

    case "FETCH_DATA_ERROR":
      return {
        ...state,
        checks: [],
        pagination: INITIAL_PAGINATION,
        doFetch: false
      };
    ...

  const onFilterInputChange = e => {
    dispatch({
      type: "SET_FILTER_INPUT",
      payload: {
        [e.target.name]: e.target.value
      }
    });
    dispatch({type: "FETCH_DATA_PENDING" });
  };

  const onFilterStatusChange = e => {
    dispatch({
      type: "SET_FILTER_STATE",
      payload: {
        status
      }
    });
    dispatch({type: "FETCH_DATA_PENDING" });
  };

  const onFiltersClear = () => {
    dispatch({
      type: "CLEAR_FILTERS"
    });
    dispatch({type: "FETCH_DATA_PENDING" });
  };


useEffect(() => {
  if (doFetch) {
    loadData();
  }
}, [doFetch]);

推荐阅读