How to use React Redux Hooks to load spinners

Viewed 3282

I am trying to load spinner using react-redux hooks (useSelector and useDispatch). I am able to fetch data but not loader (in my case showLoader and hideLoader)

Expectation: when I click the refresh button I want to load spinner (in background it will refresh the data). Before clicking the button I am able to fetch data using useEffect hook.

//ActionCreators.js
export const EVENT_LOG = "EVENT_LOG";
export const EVENT_FAILURE = "EVENT_FAILURE";
export const SHOW_LOADER = "SHOW_LOADER";
export const HIDE_LOADER = "HIDE_LOADER";

//Actions.js

    import {
      EVENT_LOG,
      EVENT_FAILURE,
      SHOW_LOADER,
      HIDE_LOADER,
    } from "./actionCreators";
    import { readList } from "./APIUtilsNew";

    export const readLogs = (path) => {
      return (dispatch) => {
        readList(path)
          .then((data) =>
            dispatch(
              {
                type: EVENT_LOG,
                payload: data,
              },
              console.log("EventLog Actions: ", data)
            )
          )
          .catch((error) => {
            dispatch({
              type: EVENT_FAILURE,
              payload: error,
            });
            throw error;
          });
      };
    };

    export const showLoader = () => (dispatch) => {
      dispatch({
        type: SHOW_LOADER,
      });
    };

    export const hideLoader = () => (dispatch) => {
      dispatch({
        type: HIDE_LOADER,
      });
    };

 //Reducers.js

    import {
      EVENT_LOG,
      EVENT_FAILURE,
      HIDE_LOADER,
      SHOW_LOADER,
    } from "../../actionCreators/index";

    export const initialState = {
      loading: false,
      eventData: [],
      eventError: false,
    };

    const eventReducer = (state = initialState, action) => {
      switch (action.type) {
        case EVENT_LOG:
          return {
            ...state,
            eventData: action.payload,
          };
        case EVENT_FAILURE:
          return {
            ...state,
            eventError: action.payload,
          };
        case HIDE_LOADER:
          return {
            ...state,
            loading: false,
          };
        case SHOW_LOADER:
          return {
            ...state,
            loading: true,
          };

        default:
          return state;
      }
    };

    export default eventReducer;

 //React Component

    import React, { useEffect } from "react";
    import { useSelector, useDispatch } from "react-redux";
    import { readLogs, showLoader, hideLoader } from "./eventActions";
    import { FormattedMessage } from "react-intl";
    import { XGrid } from "@material-ui/x-grid";
    import { CSVLink } from "react-csv";
    import IconBtn from "./IconBtn";
    import MaterialTheme from "./MaterialTheme";
    import { ThemeProvider as MuiThemeProvider } from "@material-ui/core/styles";
    import Refresh from "./Refresh";

    export default function EventsLog() {
      const dispatch = useDispatch();
      const eventLogs = useSelector(
        (state) => state.eventReducer.eventData.data || []
      );
    
      const show = useSelector((state) => state.eventReducer.loading); 
      const hide = useSelector((state) => state.eventReducer.loading);

      useEffect(() => {
        dispatch(readLogs("/events"));
      }, [dispatch]);

      const update = () => {
        dispatch(showLoader());
        dispatch(hideLoader());
      };

      let rows = eventLogs.map((obj, index) => {
        return (rows = {
          id: index + 1,
          Time: obj.time,
          dateTime: obj.dateTime,
          ID: obj.deviceId
        });
      });

      const columns = [
        {
          field: "Time",
          flex: 1,
          type: "dateTime",
          renderHeader: () => <FormattedMessage id={"time"} />
        },
        {
          field: "dateTime",
          flex: 1,
          type: "dateTime",
          renderHeader: () => <FormattedMessage id={"dateTime"} />
        },
        {
          field: "ID",
          flex: 1,
          renderHeader: () => <FormattedMessage id={"id"} />
        }
      ];

      return (
        <div>
          <h1>
            <FormattedMessage id="event.eventLog" />
            <span>
              <IconBtn iconLabel="refresh" />
            </span>
            <CSVLink data={rows} filename={"Log.csv"}>
              <IconBtn iconLabel="cloud_download" onClick={update} />
            </CSVLink>
          </h1>
          <div style={{ height: "90%", width: "100%" }}>
            <MuiThemeProvider theme={MaterialTheme}>
              <Refresh />
              <XGrid
                pageSize={50}
                rowsPerPageOptions={[25, 50, 100]}
                rows={rows}
                columns={columns}
                pagination={true}
                hideFooterSelectedRowCount={true}
              />
            </MuiThemeProvider>
          </div>
        </div>
      );
    }

This is the component where my spinner resides. I want to fetch this component while loading spinner

//Refresh Component

    import React from "react";

    export default function Refresh() {

    return <div>Spinner....</div>;
    }

I saw few examples online, where I found everything is in class components

 //  component Example

    class FullPageLoader extends Component {
        state = {  }


        render() { 
            const {loading} = this.props;

            if(!loading) return null;

            return ( 
                <div class="loader-container">
                    <div className="loader">
                        <img src={LoaderGif} />
                    </div>
                </div>
             );
        }
    }


    const mapStateToProps = state => ({ loading: state.application.loading })

    export default connect(mapStateToProps)(FullPageLoader);

    // Another Component

     updateProfile = () =>{
        this.props.dispatch( showLoader() )

        Axios.post(`https://jsonplaceholder.typicode.com/users`, { user : { name : 'Test User' } })
          .then(res => {
            console.log( res );

            this.props.dispatch( hideLoader() )
          })
        /* setTimeout(() => {
          this.props.dispatch( hideLoader() )
        }, 2000); */

      }
      
      <Button bsStyle="info" pullRight fill onClick={this.updateProfile} >
                          Update Profile
                        </Button>

Can somebody help me how to convert the above class to functional based component and instead of using mapStateToProps to hooks (or) please tell me how to load the spinner using react-redux hooks. I appreciate the help!

1 Answers

More easier way is to show and hide the loader in the action itself. Before the promise, setLoader as true. And in then and catch you can hide loader.

export const readLogs = (path) => {
      return (dispatch) => {
        showLoader();
        readList(path)
          .then((data) => {
            hideLoader();
            dispatch(
              {
                type: EVENT_LOG,
                payload: data,
              },
              console.log("EventLog Actions: ", data)
            )
          })
          .catch((error) => {
            hideLoader();
            dispatch({
              type: EVENT_FAILURE,
              payload: error,
            });
            throw error;
          });
      };
    };

if it has to be done in the component itself, You can add a delay rather than calling them immediately. There doesn't seem to be any action that is happening here.

const update = () => {
     dispatch(showLoader());
     setTimeout(() => { 
       dispatch(hideLoader());
     }, 1000);
};
Related