Получать данные вручную, используя useReducer - PullRequest
3 голосов
/ 03 марта 2020

Мне нужно получить данные с сервера, используя loadData() не только для ComponentDidMount, но и после методов onFiltersClear, onApplyFilters и onPageChange. Как я мог это сделать? Нужен ли здесь какой-то пользовательский хук для извлечения данных или есть другие способы сделать это?

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

Ответы [ 2 ]

3 голосов
/ 03 марта 2020

Ключом к тому, чтобы заставить загрузочные функции реагировать на изменения фильтров, является добавление фильтров в список зависимостей useEffect().

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

, хотя я не вижу loadStatusList() в зависимости от фильтров, поэтому рекомендация состоит в том, чтобы разделить два asyn c вызова на их собственные эффекты

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

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

Наконец, функции load * воссоздаются при каждом рендеринге, так что вы можете переместить их в соответствующие эффекты или обернуть их в useCallback(), или переместите их за пределы компонента (но тогда вам придется передавать все параметры вместо использования замыкания).

useEffect(() => {

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

  loadData();
}, [filters]);

useEffect(() => {

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

  loadStatusList();
}, []);

Я могу видеть из комментариев, что у вас нет подключил события (нажатия кнопок) и изменения состояния.

  1. Побочные эффекты, особенно асинхронные c единицы должны быть завернуты в 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 является входом и выходом эффекта, поэтому при обновлении может получить обратную связь l oop.

Чтобы быть в безопасности, мы можем добавить переменную состояния, которая контролирует выборку

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]);
1 голос
/ 03 марта 2020

Вы можете вызвать эффекты вручную, используя ссылки. Это часто полезно при использовании useReducer, который не может вызывать побочные эффекты.

 let fireFiltersClearEffect = React.useRef(false);

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

    fireFiltersClearEffect.current = true;
  };

 React.useEffect(() => {
   if (fireFiltersClearEffect.current !== false) {
      loadData();
      fireFiltersClearEffect.current = false;
   }
  });

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...