Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent UI freeze when calling API with axios

I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.

I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.

I am working on linux using React 16.8 (when using RN I use 55.4)

I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.

I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.

async componentDidMount() {
    await getTasks().then();
}

And I tried this

export const getTasks = () => (async (dispatch, getState) => {
    return await axios.get(`${URL}`, AJAX_CONFIG).then();
}

Current Code:

Component.js

componentDidMount() {
    const { userIntegrationSettings, getTasks } = this.props;
    // Sync our list of external API tasks
    if (!isEmpty(userIntegrationSettings)) {
        getTasks(userIntegrationSettings.token)
            // After we fetch our data from the API create a mapping we can use
            .then((tasks) => {
                Object.entries(tasks).forEach(([key, value]) => {
                    Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
                });
            });
    }
}

Action.js

export const getTasks = () => ((dispatch, getState) => {
    const state = getState();
    const { token } = state.integrations;

    const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
    const AJAX_CONFIG = getAjaxHeaders(token);

    dispatch(setIsFetchingTasks(true));

    return axios.get(`${URL}`, AJAX_CONFIG)
        .then((response) => {
            if (!isEmpty(response.data)) {
                response.data.forEach((task) => {
                    dispatch(addTask(task));
                });

                return response.data;
            } else {
                dispatch(setIsFetchingTasks(false));
            }
        })
        .catch((error) => {
            dispatch(setIsFetchingTasks(false));
            errorConsoleDump(error);
            errorHandler(error);
        });
});

reducer.js

export default (state = defaultState, action) => {
    switch (action.type) {
        case ADD_TASK:
        case UPDATE_TASK:
            return update(state, {
                byTaskId: { $merge: action.task },
                isFetching: { $set: false }
            });
        default:
            return state;
    }
};
like image 296
P-Rick Stephens Avatar asked Dec 11 '22 03:12

P-Rick Stephens


1 Answers

So in my answer what are you going to learn?

  • General data loading with Redux
  • Setting up a component lifecycle method such as componentDidMount()
  • Calling an action creator from componentDidMount()
  • Action creators run code to make an API request
  • API responding with data
  • Action creator returns an action with the fetched data on the payload property

Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.

So instead of this:

async componentDidMount() {
  await getTasks().then();
}

try this:

componentDidMount() {
  this.props.fetchTasks();
}

So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.

The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.

So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.

So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.

To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:

import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";

You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)

I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.

Instead of this:

export const getTasks = () => (async (dispatch, getState) => {
    return await axios.get(`${URL}`, AJAX_CONFIG).then();
}

try this:

export const fetchTasks = () => async dispatch => {
  const response = await axios.get(`${URL}`, AJAX_CONFIG);
  dispatch({ type: "FETCH_TASKS", payload: response.data });
};

There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.

This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.

You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:

import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

import App from "./components/App";
import reducers from "./reducers";

const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")

Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.

The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.

Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.

Why is Redux-Thunk necessary?

It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.

Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.

If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.

Redux-Thunk allows you to return either an action or function within your action creator.

But why is this important? Who cares if it returns an action object or a function? What does it matter?

That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.

So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.

So any action creator that makes an network request qualifies as an asynchronous action creator.

Network requests with JavaScript are asynchronous in nature.

So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.

like image 93
Daniel Avatar answered Dec 28 '22 06:12

Daniel