Реагировать: получать информацию асинхронно с помощью useReducer и useContext

Я пытаюсь воспроизвести то, что я делал с Reactjs/Redux/redux-thunk:

  • Показать счетчик (во время загрузки)
  • Получить информацию с удаленного сервера
  • отображать информацию и удалять счетчик

Подход заключался в использовании useReducer и useContext для моделирования редукции, как описано в этом руководство. Что касается асинхронной части, я полагался на redux-thunk, но я не знаю, есть ли ему альтернатива для useReducer. Вот мой код:

Сам компонент:

  const SearchForm: React.FC<unknown> = () => {
  const { dispatch } = React.useContext(context);
  // Fetch information when clickin on button
  const getAgentsInfo = (event: React.MouseEvent<HTMLElement>) => {
    const fetchData:() => Promise<void> = async () => {
      fetchAgentsInfoBegin(dispatch);           //show the spinner
      const users = await fetchAgentsInfo();    // retrieve info  
      fetchAgentsInfoSuccess(dispatch, users);  // show info and remove spinner
    };
    fetchData();
  }
  return (
   ...
  )

Файл сборщика данных:

export const fetchAgentsInfo:any = () => {
  const data = await fetch('xxxx');
  return await data.json();
};

Файлы действий:

export const fetchAgentsInfoBegin = (dispatch:any) => {
  return dispatch({ type: 'FETCH_AGENTS_INFO_BEGIN'});
};

export const fetchAgentsInfoSuccess = (dispatch:any, users:any) => {
  return dispatch({
    type: 'FETCH_AGENTS_INFO_SUCCESS',
    payload: users,
  });
};

export const fetchAgentsInfoFailure = (dispatch:any) => {
  return dispatch({
    type: 'FETCH_AGENTS_INFO_FAILURE'
  })
};

И сам мой магазин:

import React, { createContext, useReducer } from 'react';
import {
  ContextArgs, 
  ContextState, 
  ContextAction
} from './types';

// Reducer for updating the store based on the 'action.type'
const Reducer = (state: ContextState, action: ContextAction) => {
  switch (action.type) {
    case 'FETCH_AGENTS_INFO_BEGIN':
      return { 
        ...state,
        isLoading:true,
      };
    case 'FETCH_AGENTS_INFO_SUCCESS':
      return { 
        ...state,
        isLoading:false,
        agentsList: action.payload,
      };
    case 'FETCH_AGENTS_INFO_FAILURE':
      return { 
        ...state,
        isLoading:false,
        agentsList: [] };
    default:
      return state;
  }
};

const Context = createContext({} as ContextArgs);

// Initial state for the store
const initialState = {
  agentsList: [],
  selectedAgentId: 0,
  isLoading:false,
};

export const ContextProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(Reducer, initialState);
  const value = { state, dispatch };
  Context.displayName = 'Context';
  return (
    <Context.Provider value={value}>{children}</Context.Provider>
  );
};

export default Context;

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

Ваша помощь будет оценена! Спасибо


person johann    schedule 27.06.2020    source источник
comment
Можете ли вы добавить свой код рендеринга к вопросу, пожалуйста? Я не вижу ничего плохого в коде, который вы разместили   -  person ludwiguer    schedule 28.06.2020
comment
Спасибо за чтение. Когда вызывается действие «FETCH_AGENTS_INFO_BEGIN», счетчик должен отображаться и удаляться с помощью действия «FETCH_AGENTS_INFO_SUCCESS». Если нет асинхронного действия, оно работает. Но если я использую «выборку», «аксиомы» или другую асинхронную логику, счетчик никогда не отображается. Я пробовал с setTimeOut, но так как этот метод тоже асинхронный, результат тот же.   -  person johann    schedule 28.06.2020
comment
Решение, представленное здесь[stackoverflow.com/a/67242248/3400445], очень быстрое и простое.   -  person JJorian    schedule 24.04.2021


Ответы (1)


Я не вижу в опубликованном вами коде ничего, что могло бы вызвать описанную вами проблему, возможно, сделайте console.log в редюсере, чтобы увидеть, что происходит.

У меня есть предложение изменить код и перенести логику из компонента в действие, используя своего рода переходное действие и заменив магические строки константами:

//action types
const BEGIN = 'BEGIN',
  SUCCESS = 'SUCCESS';
//kind of thunk action (cannot have getState)
const getData = () => (dispatch) => {
  dispatch({ type: BEGIN });
  setTimeout(() => dispatch({ type: SUCCESS }), 2000);
};
const reducer = (state, { type }) => {
  if (type === BEGIN) {
    return { ...state, loading: true };
  }
  if (type === SUCCESS) {
    return { ...state, loading: false };
  }
  return state;
};
const DataContext = React.createContext();
const DataProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, {
    loading: false,
  });
  //redux-thunk action would receive getState but
  //  cannot do that because it'll change thunkDispatch
  //  when state changes and could cause problems when
  //  used in effects as a dependency
  const thunkDispatch = React.useCallback(
    (action) =>
      typeof action === 'function'
        ? action(dispatch)
        : action,
    []
  );
  return (
    <DataContext.Provider
      value={{ state, dispatch: thunkDispatch }}
    >
      {children}
    </DataContext.Provider>
  );
};
const App = () => {
  const { state, dispatch } = React.useContext(DataContext);
  return (
    <div>
      <button
        onClick={() => dispatch(getData())}
        disabled={state.loading}
      >
        get data
      </button>
      <pre>{JSON.stringify(state, undefined, 2)}</pre>
    </div>
  );
};
ReactDOM.render(
  <DataProvider>
    <App />
  </DataProvider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>

person HMR    schedule 28.06.2020