How to prevent UI freeze when calling API with axios - reactjs

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

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.

You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish

Related

React Js - Combine Redux and Services layers

After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}

Redux-saga: Calling a saga outside the redux middleware environment

Pretty new to redux saga - I'm trying to figure out how to call a saga outside the redux middleware environment when I have access to the store.
After reading the redux saga docs looks like I have two options either call store.runSaga or use the runSaga utility provided by redux-saga.
This is what I'm trying to do:
Steps:
Created a saga that pauses until the success action is dispatched.
Something like this:
export function* loadDashboardSequenced() {
try {
// Take pauses until action received.
//Wait for the user to be loaded
const user_success = yield take('FETCH_USER_SUCCESS');
// ....do other stuff....
} catch(error) {
yield put({type: 'FETCH_FAILED', error: error.message});
}
Now I'm trying to call the saga either via store.runSaga https://redux-saga.js.org/docs/api/ or runSaga https://redux-saga.js.org/docs/api/index.html#runsagaoptions-saga-args
Is there any benefits of using runSaga vs store.runSaga? I'm not sure which one I should use at this point. Any thoughts/suggestions? Thanks!
EDIT: followup question about using runSaga https://redux-saga.js.org/docs/advanced/UsingRunSaga.html
What does this line mean
subscribe: ..., // this will be used to resolve take Effects
The store.runSaga method is used to start the root saga for the store. It also expects redux-saga to be a store middleware :
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware()
return {
...createStore(reducer, initialState, applyMiddleware(/* other middleware, */sagaMiddleware)),
runSaga: sagaMiddleware.run
}
}
const store = configureStore()
store.runSaga(rootSaga)
On the other hand, runSaga method is there for connecting redux-saga to non-store objects and interfaces, which is something you rarely do.
In summary, if you need sagas put and take methods to work with redux actions, then you you need to use redux-saga as store middleware.
If you have a saga that retrieves data from a REST API and then hands off the data to a reducer ... then testing your saga without a store makes a lot of sense.
Furthermore, the thing I didn't like about the Beginner Tutorial tests, is that I wanted to test the full stack, client through to server. The tests in "Beginner Tutorial" cannot test that because they do not actually get the results of the saga's execution, just the call/put messages.
I would look at The best way to test Redux Sagas for full discussion, but here is a quick sample that summarizes Phil Herbert's post:
import { runSaga } from 'redux-saga'; // using runSaga to execute the saga
import { testMeSaga } from './sagas'; // the saga under test
test('test testMeSaga by running it', async () => {
// 'recordSaga' is a utility function for running sagas, see it below.
const result = await recordSaga(
testMeSaga,
{ payload: 'your input to the saga' }
);
expect(result.length).toBe(1);
// add more checks here ...
});
// this is the utility function that wraps the running of the saga
async function recordSaga(saga, initialAction) {
const dispatched = [];
await runSaga(
{
dispatch: (action) => dispatched.push(action)
},
saga,
initialAction
).done;
return dispatched;
}

React Redux Thunk trigger multiple actions on one call

I have an action which in turn must affect many other areas of my app state. In this case, when the user selects a website from a dropdown list, it must update many other components. I'm currently doing it like so:
setSelectedWebsite (websiteId) {
// The core reason for this component
this.props.setSelectedWebsite(websiteId);
// Fetch all associated accounts
this.props.fetchAllAccounts(websiteId)
// Several other "side effect" calls here...
}
In this interest of making one component serve one purpose, this feels like a bad practice.
What is the best practice for triggering multiple actions in one call from a component?
You could use redux-thunk.
Your component's method:
setSelectedWebsite(websiteId){
this.props.handleSetSelectedWebsite(websiteId) // this is a thunk
}
Your Redux file with action creators / thunks:
// this function is a thunk
export const handleSetSelectedWebsite = (websiteId) => (dispatch) => {
dispatch(setSelectedWebsite(websiteId))
dispatch(fetchAllAccounts(websiteId))
}
// these function are action creators (or could be other thunks if you style them the same way as the thunk above)
const setSelectedWebsite = (websiteId) => {
// your code
}
const fetchAllAccounts = (websiteId) => {
// your code
}
For handling complex side effects in a redux application, I would recommend looking at using Redux Sagas. I have seen its usage grow in popularity on projects large and small, and for good reason.
With sagas, in the example you have provided, you can emit a single action from a function provided through mapDispatchToProps and let a saga take care of the rest. For example: (following example assumes flux standard actions)
//import redux connect, react, etc
class SiteSelector extends React.Component {
render() {
const id = this.props.id;
return (
<button onClick={ () => this.props.action(id)>Click Me</button>
)
}
}
const mapStateToProps = (state) => ({
id: state.websiteId
})
const mapDispatchToProps = dispatch => ({
action: (id) => dispatch(setSelectedWebsite(id))
})
export connect(mapStateToProps, mapDispatchToProps)(SiteSelector)
Now you can handle the action emitted from setSelectedWebsite in a saga like so:
//import all saga dependencies, etc
export function* selectSite(action) {
const id = action.payload.id
yield put(Actions.selectWebsite(id))
const results = yield call(Api.fetchAllAccounts, id)
yield //do something complex with results
yield //do something else...
yield //and keep going...
}
// Our watcher Saga: spawn a new selectSite task every time the action from setSelectedWebsite is dispatched
export function* watchForSiteSelection() {
yield takeEvery('SITE_SELECTED_ACTION', selectSite)
}
For reference checkout the docs: Redux Sagas

How to make API calls using React, Redux in TypeScript

I'm using typescript-fsa and typescript-fsa-reducers packages to simply create actions and reducers in TypeScript React application.
const actionCreator = actionCreatorFactory();
export function signInHandler(state: UserState, action: Action): UserState {
// ????
return { ...state };
}
export const signIn = actionCreator.async<SignInRequest, RequestResponse<SignInResponse>>("USER_SIGNIN");
export const UserReducer = reducerWithInitialState({ signedIn: false } as UserState)
.casesWithAction([signIn.started, signIn.done], signInHandler)
.build();
Usage in component:
export default connect<StateProps, DispatchProps>(
(state: RootState) => ({} as StateProps),
(dispatch: Dispatch<RootState>) => {
return {
signIn: (userName: string, password: string) => dispatch(signIn.started(new SignInRequest(userName, password)))
};
}
)(SignIn);
And now I'm stuck. I don't know how to make HTTP calls to my API so I can send request when component dispatches action on dispatch next action when response from API arrives. I would like to use promises.
How to solve that?
In React without the typescript-fsa abstraction, you'd make async API callsat the action creator level, since actions are just dispatched POJOs and reducers are supposed to not have any side effects.
There are two projects that make it easy to do this, redux-thunk and redux-saga. I prefer redux-thunk because it is easier to wrap your head around. Basically your action creators get passed the dispatch function, and then they can be responsible for dispatching more than one thing... like so:
function asyncActionCreator(dispatch) {
dispatch(startAsyncAction());
doSomethingAsync()
.then(result => dispatch(completeAsyncAction(result))
.catch(err => dispatch(errorAsyncAction(err));
}
In your typescript-fsa world, there are some companion packages for both of these: typescript-fsa-redux-thunk and typescript-fsa-redux-saga.
It appears that typescript-fsa-redux-thunk takes a similar approach to the above example, using the concept of an "action worker", which coordinates the dispatching of actions via typescript-fsa. There is a really good example of doing this on the typescript-fsa-redux-thunk repo.

Dispatching an action from an asynch call in Redux

I am converting my project from flux to redux.
The root component is very straight forward redux :
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux'
import AppReducer from './AppReducer'
import { createStore } from 'redux'
const store = createStore(AppReducer)
ReactDOM.render(
<Provider store={store}>
//rest of application goes here
</Provider>,
app);
Using mapToProps, mapToDispatch I can deal with behaviour at the compoment level just fine.
Now I am coming to server responses, ie asynch calls.
In my flux implementation I have a file a call 'Server' I have my AJAX call and a promise is returned which then calls AppDispatcher.dispatch :
function handleResponse(endpoint) {
return function (err, res) {
if (res && res.ok) {
receiveData(endpoint, res);
}
};
}
function receiveData(endpoint, responseData) {
AppDispatcher.dispatch(
{
type: ActionTypes.SERVER_RESPONSE,
endpoint: endpoint,
state: "ReponseIsOK",
payload: responseData.body
}
);
dispatch(endpoint, "CommunicationState.RESPONSE_OK", responseData.body);
}
How should I convert this into redux?
I need to somehow get to the dispatcher, and I was hoping something like this would work :
function receiveData(endpoint, responseData) {
let store = Provider.getStore();
store.dispatch(
{
type: ActionTypes.SERVER_RESPONSE,
endpoint: endpoint,
state: "ReponseIsOK",
payload: responseData.body
}
);
}
I have tried provider.store, provider.context.store and they also seem to not work.
I really just need access to the store so that I can fire events, but not sure how to access it.
I'm pretty sure this is a situation to use Redux-Thunk, which allows you to dispatch actions asynchronously.
Here is the explanation from the docs:
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
I think you would first need to modify your creation of the store and pass in redux-thunk as a middleware and then you can modify your receiveData function to be something like this:
function receiveData(endpoint, responseData) {
return (dispatch, getState) => {
// DISPATCH YOUR ACTION WITH RESPONSE DATA
// AND ACCESS THE REDUX STORE
}
}
You just need to save a reference to the store when you create it with configureStore. You could just set up a getter if you'd like to access this from outside.
Pseudo code:
var store = configureStore()
// ...
export function getStore() {
return store
}
// ...
<Provider store={ store } />

Resources