1

I am trying to reproduce something I was doing with Reactjs/ Redux/ redux-thunk:

  • Show a spinner (during loading time)
  • Retrieve information from remote server
  • display information and remove spinner

The approach was to use useReducer and useContext for simulating redux as explained in this tutorial. For the async part, I was relying on redux-thunk, but I don't know if there is any alternative to it for useReducer. Here is my code:

The component itself :

  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 (
   ...
  )

The data fetcher file :

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

The Actions files:

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

And my store itself :

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;

I tried to partially reuse logic from this article but the spinner is never displayed (data are properly retrieved and displayed).

Your help will be appreciated ! Thanks

johann
  • 1,115
  • 8
  • 34
  • 60
  • Can you add your render code to the question, please? I can’t see anything wrong with the code you posted – ludwiguer Jun 28 '20 at 01:47
  • Thanks for reading. When the Action 'FETCH_AGENTS_INFO_BEGIN' is called, a spinner is supposed to be displayed, and removed with the 'FETCH_AGENTS_INFO_SUCCESS' action. If no async action, it works. But if I use 'fetch', 'axios' or other async logic, spinner is never displayed. I tried with a 'setTimeOut', but since this method is also async, result is the same. – johann Jun 28 '20 at 05:56
  • The solution provided here[https://stackoverflow.com/a/67242248/3400445] is very fast and easy – JAvAd Apr 24 '21 at 11:27

1 Answers1

2

I don't see anything in the code you posted that could cause the problem you describe, maybe do console.log in the reducer to see what happends.

I do have a suggestion to change the code and move logic out of the component and into the action by using a sort of thunk action and replacing magic strings with constants:

//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>
HMR
  • 37,593
  • 24
  • 91
  • 160