To add loading svg image for async request api - reactjs

I want to add custom svg image in my code for loading till the response comes from api called.
I have tried adding flag but it's not working.
Please help me how can i add svg image for loading. I am in learning phase of react and redux.
I have processChatMessage for requesting the api call.
The reducer.js is like:
const processChatMessage = (state, action) => {
console.log("[reducer]", action.type);
const { messages } = state;
const { message } = action.payload;
const newMessages = [...messages, message];
return { ...state, messages: newMessages };
};
const commitChatMessageRequest = processChatMessage;
const commitChatMessageSuccess = processChatMessage;
const commitChatMessageError = processChatMessage;
// Hub Reducer
const ChatMessageReducer = (state = initState, action) => {
let newState;
switch (action.type) {
case types["CHAT/MESSAGE_REQUEST"]:
newState = commitChatMessageRequest(state, action);
break;
case types["CHAT/MESSAGE_SUCCESS"]:
newState = commitChatMessageSuccess(state, action);
break;
case types["CHAT/MESSAGE_ERROR"]:
newState = commitChatMessageError(state, action);
break;
default:
newState = state;
}
return newState;
};
export default ChatMessageReducer
And the action.js is as below:
import types from "./action-types";
let isErrorMessage = false;
let onProcess = false;
let sender = 'Bot'
let error = null;
// action creator
export const msgChatMessageRequest = text => ({
type: types["CHAT/MESSAGE_REQUEST"],
payload: {
message: {
text,
sender: "User",
isErrorMessage
},
onProcess: true,
error
}
});
export const msgChatMessageSuccess = text => ({
type: types["CHAT/MESSAGE_SUCCESS"],
payload: {
message: {
text,
sender,
isErrorMessage
},
onProcess,
error
}
});
export const msgChatMessageError = error => ({
type: types["CHAT/MESSAGE_ERROR"],
payload: {
message: {
text: "Sorry! " + error.message,
sender,
isErrorMessage: true
},
onProcess,
error
}
});

Since you are new here, quick advise : just the "it's not working" is never sufficient. Really try to describe the expected behaviour (you did it) but also the current behaviour. For example here I have no idea of what is happening : it the onProcess set to true at anytime ? Is it just maybe a problem of you not succeeding to implement the image itself in your code ? ... So much possibilities.
Anyway I even do not see any call to an API in your code so again, it is difficult to see where the error is coming from...
BUT positive thing, I see that you had the good intuition to use an onProcess boolean in your store, I guess to track the state of your pending request.
Now the logic you need to implement is with 1 action and 3 reducers :
FetchChatMessage : First call IsFetching reducer to pass your onProcess to True, then call your API, then on answer call OnChatMessageSucces reducer if success, or OnChatMessageFail on failure and on each of these methods set your onProcess to false.
Here is a quick example of what you are actually trying to do : https://jsfiddle.net/ibobcode/fgk917bz/5/
.

Related

Who to load dropdown options from API in react JS with typescript and react saga?

Here is my page, Here I want to load brand option from API.
I have written saga attached below:
Action.tsx
export const getBrandsForDropdown = (request: IPagination) => {
return {
type: actions,
payload: request
}
}
Api.tsx
export const getBrandsForDropdown = async () => {
const page = 1;
const limit = 1000;
console.log("get brand drop down");
const query = `user/master/brands?page=${page}&limit=${limit}`;
return client(query, { body: null }).then(
(data) => {
console.log("get brand drop down in ");
return { data, error: null };
},
(error) => {
return { data: null, error };
}
);
};
Reducer.ts
case actions.GET_BRANDS_DROPDOWN_PENDING:
return {
...state,
loading: true,
};
case actions.GET_BRANDS_DROPDOWN_REJECTED:
return {
...state,
loading: false,
};
case actions.GET_BRANDS_DROPDOWN_RESOLVED:
return {
...state,
loading: false,
brandOptions: action.payload,
};
Saga.ts
function* getBrandForDropDownSaga(action: HandleGetBrandsForDropdown) {
yield put(switchGlobalLoader(true));
yield put(pendingViewBrand());
try {
const { data } = yield getBrandsForDropdown();
yield put(resolvedViewBrand(data));
yield put(switchGlobalLoader(false));
} catch (error) {
yield put(switchGlobalLoader(false));
return;
}
}
After this I don't how to call it in my page and get it as a options in brand dropdown
Original Answer: Just Use Thunk
You can do this with redux-saga but I wouldn't recommend it. redux-thunk is a lot easier to use. Thunk is also built in to #reduxjs/toolkit which makes it even easier.
There is no need for an IPagination argument because you are always setting the pagination to {page: 1, limit: 1000}
Try something like this:
import {
createAsyncThunk,
createSlice,
SerializedError
} from "#reduxjs/toolkit";
import { IDropdownOption } from "office-ui-fabric-react";
import client from ???
// thunk action creator
export const fetchBrandsForDropdown = createAsyncThunk(
"fetchBrandsForDropdown",
async (): Promise<IDropdownOption[]> => {
const query = `user/master/brands?page=1&limit=1000`;
return client(query, { body: null });
// don't catch errors here, let them be thrown
}
);
interface State {
brandOptions: {
data: IDropdownOption[];
error: null | SerializedError;
};
// can have other properties
}
const initialState: State = {
brandOptions: {
data: [],
error: null
}
};
const slice = createSlice({
name: "someName",
initialState,
reducers: {
// could add any other case reducers here
},
extraReducers: (builder) =>
builder
// handle the response from your API by updating the state
.addCase(fetchBrandsForDropdown.fulfilled, (state, action) => {
state.brandOptions.data = action.payload;
state.brandOptions.error = null;
})
// handle errors
.addCase(fetchBrandsForDropdown.rejected, (state, action) => {
state.brandOptions.error = action.error;
})
});
export default slice.reducer;
In your component, kill the brandOptions state and access it from Redux. Load the options when the component mounts with a useEffect.
const brandOptions = useSelector((state) => state.brandOptions.data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchBrandsForDropdown());
}, [dispatch]);
CodeSandbox Link
Updated: With Saga
The general idea of how to write the saga is correct in your code.
take the parent asynchronous action.
put a pending action.
call the API to get data.
put a resolved action with the data or a rejected action with an error.
The biggest mistakes that I'm seeing in your saga are:
Catching errors upstream.
Mismatched data types.
Not wrapping API functions in a call effect.
Error Handling
Your brands.api functions are all catching their API errors which means that the Promise will always be resolved. The try/catch in your saga won't have errors to catch.
If you want to catch the errors in the saga then you need to remove the catch from the functions getBrandsForDropdown etc. You can just return the data directly rather than mapping to { result: data, error: null }. So delete the whole then function. I recommend this approach.
export const getBrandsForDropdown = async () => {
const page = 1;
const limit = 1000;
const query = `user/master/brands?page=${page}&limit=${limit}`;
return client(query, { body: null });
}
If you want to keep the current structure of returning a { result, error } object from all API calls then you need to modify the saga to look for an error in the function return.
function* getBrandForDropDownSaga() {
yield put(switchGlobalLoader(true));
yield put(pendingGetBrands());
const { data, error } = yield call(getBrandsForDropdown);
if (error) {
yield put(rejectedGetBrands(error.message));
} else {
yield put(resolvedGetBrands(data));
}
yield put(switchGlobalLoader(false));
}
Mismatched Data Types
There's some type mismatching in your reducer and state that you need to address. In some places you are using an array IBrand[] and in others you are using an object { results: IBrand[]; totalItems: number; currentPage: string; }. If you add the return type IState to the reducer then you'll see.
There's also a mismatch between a single IBrand and an array. I don't know the exact shape of your API response, but getBrandsForDropdown definitely has an array of brands somewhere. Your saga getBrandForDropDownSaga is dispatching resolvedViewBrand(data) which takes a single IBrand instead of resolvedGetBrands(data) which takes an array IBrand[]. If you add return types to the functions in your brands.api file then you'll see these mistakes highlighted by Typescript.
Don't Repeat Yourself
You can do a lot of combining in your API and your saga between the getBrands and the getBrandsForDropdown. Getting the brands for the dropdown is just a specific case of getBrands where you set certain arguments: { page: 1, limit: 1000 }.
export interface IPagination {
page?: number;
limit?: number;
sort?: "ASC" | "DESC";
column?: string;
}
export const getBrands = async (request: IPagination): Promise<IBrands> => {
const res = await axios.get<IBrands>('/user/master/brands', {
params: request,
});
return res.data;
};
function* coreGetBrandsSaga(request: IPagination) {
yield put(switchGlobalLoader(true));
yield put(pendingGetBrands());
try {
const data = yield call(getBrands, request);
yield put(resolvedGetBrands(data));
} catch (error) {
yield put(rejectedGetBrands(error?.message));
}
yield put(switchGlobalLoader(false));
}
function* getBrandsSaga(action: HandleGetBrands) {
const { sort } = action.payload;
if ( sort ) {
yield put(setSortBrands(sort));
// what about column?
}
const brandsState = yield select((state: AppState) => state.brands);
const request = {
// defaults
page: 1,
limit: brandsState.rowsPerPage,
column: brandsState.column,
// override with action
...action.payload,
}
// the general function can handle the rest
yield coreGetBrandsSaga(request);
}
function* getBrandsForDropDownSaga() {
// handle by the general function, but set certain the request arguments
yield coreGetBrandsSaga({
page: 1,
limit: 1000,
sort: "ASC",
column: "name",
})
}
export default function* brandsSaga() {
yield takeLatest(HANDLE_GET_BRANDS, getBrandsSaga);
yield takeLatest(GET_BRANDS_DROPDOWN, getBrandForDropDownSaga);
...
}
CodeSandbox

How to prevent non-deterministic state updation in Redux?

When working with Redux, maintaining the shape of the initial state is crucial. The results/data of side effects like API call will change the shape of the state since we have no control over the properties. For example, consider this initial state:
const book = {
id: 0,
name: 'something'
};
And updation is made to it by the book sub-reducer as follows based on the API data:
//receives `book` part of the state
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
}
Two scenarios that could happen:
If the data sent from the API is null, then newly produced state is now {} as a result of the spread operator. If some parts of UI were to listen to the book part of the state, then it will break. Possibly access individual properties from the API data? In that case, null/undefined checks needs to be performed for properties. Is there a more elegant solution?
There could also be additional properties in the data which we may not be interested in. Possibly use an object mapper to filter unused properties?
What is the best practice to handle these kind of scenarios and prevent state becoming non-deterministic? Please share your experience on how you approached these scenarios.
Only the reducer has to be pure/deterministic, not the stuff outside of it.
To prevent your reducer from overwriting data incorrectly, write some logic between the API response and the dispatch-call to ensure the reducer always gets valid data.
For example a thunk might look like:
const createBook = (name) => {
return async dispatch => {
// suppose the api call gives back "uid" plus extra data
const { uid, ...unneededData } = await myApi.setBook(name);
// dispatch the data in the way the reducer expects it
dispatch({ type: 'SET_BOOK', id: uid, name });
}
}
In the above example, the api call gives me uid, but no name, and a bunch of extra data. Just prepare the data before dispatching it.
The best practice is the one where you prevent your app from breaking from every aspect, which means you need to check and format your data before returning from the reducer.
In your case, I would check for both data validity and map it to a necessary format.
only dispatch 'SET_BOOK' if API response has both id and book.
in order to avoid unnecessary additional properties, you can always map your data const book = {id: apiData.id, book: apiData.book} before dispatching.
In your reducer you can do like below. This way only id and name will get updated even though there are other key/values in the response. Also this will make sure that if null values are received then those values will not be updated in the state. Hope this will help in resolving the issue.
//receives `book` part of the state
const bookReducer = (state=book, action) => {
const { type, payload } = action;
switch(type) {
case 'SET_BOOK': {
return {
...state,
...(payload.id && {id: payload.id}),
...(payload.name && {name: payload.name})
};
} default:
return state;
}
}
Your redux reducer logic should not worry about that due to its deterministic nature. You handle your api call and response handling elsewhere (redux thunk or component), and then dispatch the action to set your redux. Building off of your example:
book.reducer.js
const book = {
id: 0,
name: ''
};
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
book.actions.js
const setBook = (book) => ({
type: SET_HEROES,
payload: book
});
// thunk
const findBook = name => async dispatch => {
const book = await bookService.findBook(name);
if (book) {
dispatch(setBook(book));
}
};
book.service.js
const findBook = async (name) => {
// Do your api call
const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);
// Handle the response
if (!bookResponse) {
// Logic you do if book not found
return null;
}
return {id: bookResponse.id, name: bookResponse.name};
}
Now in a component you can just dispatch the findBook call
Component.js
const Component = () => {
const [search, setSearch] = useState('');
const dispatch = useDispatch();
const handleOnSearch = () => {
dispatch(findBook(search));
}
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)}/>
<button onClick={handleOnSearch}>Search</button>
</div>
);
}
If field value from API is undefined then convert it into null and store so that the code doesn't break and operatable. If API gives other params as well then de-structure the API returned object and extract the required fields. So that storing unnecessary data can be avoided.
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
const {id, name, otherParam1, otherParam2} = action.payload;
return {
id: id || null,
name: name || null,
otherParam1,
otherParam2
}
} default:
return state;
}
}
Having the value null won't break the code instead, it renders nothing
which is better than undefined which breaks the code
Hope this helps you
What I do is to have all of my logic in my action method and create reducers for when an action is correctly fulfilled and another one for when is rejected. In the fulfilled reducer, I would do the regular instructions and in the rejected reducer I would add the data to a variable called error which I always have in my state and use in the frontend to show an error message if needed.
Example
This is an action that creates a house by sending a post request to my api which returns the created object or an error if something went wrong.
export const createHouse = houseData => {
const URL = HTTP://EXAMPLE.URL
return async dispatch => {
try {
const response = await axios.post(`${URL}`, houseData);
const data = await response.data;
dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
} catch (err) {
dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
}
};
};
Then I would have 2 reducer methos to recieve the fulfilled or the rejected response, like this.
case 'CREATE_HOUSE_DRAFT_FULFILLED': {
return {
houses: [action.data, ...state.houses],
house: action.data,
houseCount: state.houseCount + 1,
fetched: true,
error: null
};
}
case 'CREATE_HOUSE_DRAFT_REJECTED': {
return {
...state,
error: action.data.response.data,
fetched: false,
success: null
};
}
Hope this works for you!

Why is Thunk-Redux turning an object into a string?

I've come across a strange issue with thunk-redux. I am writing a React-Redux app that calls a public API, and displays the data in a table. However, when I incorporated thunk middleware to handle the asynchronous API calls, my data is being stringified after the action is dispatched to the reducer.
index.js (action creator)
export const FETCHING_DATA = 'FETCHING_DATA';
export const FETCH_SUCCESS = 'FETCH_SUCCESS';
export const ERROR = 'ERROR';
export const getData = () => {
return {
type : FETCHING_DATA
}
}
export const getDataSuccess = (data) => {
return {
type : FETCH_SUCCESS,
payload: data
}
}
export const getDataFailure = () => {
return {
type : ERROR
}
}
export function searchCVE(cve){
const url = `${CVE_URL}api/cve/${cve}`;
return dispatch => {
dispatch(getData());
fetch(PROXY + url)
.then(blob => blob.json())
.then(data => {
console.log('Request: ', data);
dispatch(getDataSuccess(data))
})
.catch(e => {
console.log(e);
dispatch(getDataFailure(e.message))
});
}
}
data_reducer.js (reducer)
import {FETCHING_DATA ,FETCH_SUCCESS, ERROR } from '../actions/index.js';
const initialState = {
payload:[],
fetching: false,
error: false
}
export default function(state=initialState, action){
console.log('Got CVE: ', action);
switch (action.type){
case FETCHING_DATA: return {payload:[], fetching: true, ...state}
case FETCH_SUCCESS: return [action.payload, ...state]
case ERROR: return {payload:[], error: true, ...state}
}
return state;
}
As you can see in the index.js action creator, console.log('Request: ', data); displays the JSON object I want. However, when I {console.log('TEST: ' + this.props.cve)} in my table component, the console shows:
TEST: [object Object]
At no point in my app am I "stringifying" my data - why/where could thunk-redux be turning my data into a string? I thank the community for any insight it can provide.
At no point in my app am I "stringifying" my data - why/where could thunk-redux be turning my data into a string?
redux-thunk couldn't do that under no circumstances. It's deadly simple; all it's doing is processing function action in a different way.
The problem is that the object is being stringified by you, + addition operator coerces an object to a string:
{console.log('TEST: ' + this.props.cve)}
In case an object is expected to be displayed in the console, it should be:
{console.log('TEST: ', this.props.cve)}
Or it can be displayed in DOM:
<pre>{JSON.stringify(this.props.cve, null, 2)}</pre>

Custom Websocket in Redux Architecture

The more I read about this subject it seems like going down a rabbit hole. This is a new Trading application which receives realtime data through web sockets which is based on a request-response paradigm. There are three separate SPA's in which apart from initial load, every user action triggers a call to the dataStore with a new MDXQuery. So in turn I would need to make fresh subscriptions on a componentDidMount() as well as in the respective ActionCreators.I would like to streamline the code to avoid duplicate code and redundancy.
The below code helps establish a new subscription channel to streams the response through web-socket.(Unlike, most sockets.io code where it comes with a designated open,close,send)
this.subscription = bus.channel(PATH, { mode: bus.wsModes.PULL }).createListener(this.onResponse.bind(this));
this.subscription.subscribe(MDXQuery);
If I read the REDUX documentation as to where should I place the web socket code? It mentions to create a custom middleware.
LINK: https://redux.js.org/faq/codestructure#where-should-websockets-and-other-persistent-connections-live
But I am not very sure how could I go about using this custom web socket code framing my own middleware or doing at the component level would help to mimic this strategy.
const createMySocketMiddleware = (url) => {
return storeAPI => {
let socket = createMyWebsocket(url);
socket.on("message", (message) => {
storeAPI.dispatch({
type : "SOCKET_MESSAGE_RECEIVED",
payload : message
});
});
return next => action => {
if(action.type == "SEND_WEBSOCKET_MESSAGE") {
socket.send(action.payload);
return;
}
return next(action);
}
}
}
Any design inputs would really help!!
I wrote that FAQ entry and example.
If I understand your question, you're asking about how to dynamically create additional subscriptions at runtime?
Since a Redux middleware can see every dispatched action that is passed through the middleware pipeline, you can dispatch actions that are only intended as commands for a middleware to do something. Now, I'm not sure what an MDXQuery is, and it's also not clear what you're wanting to do with the messages received from these subscriptions. For sake of the example, I'll assume that you want to either dispatch Redux actions whenever a subscription message is received, or potentially do some custom logic with them.
You can write a custom middleware that listens for actions like "CREATE_SUBSCRIPTION" and "CLOSE_SUBSCRIPTION", and potentially accepts a callback function to run when a message is received.
Here's what that might look like:
// Add this to the store during setup
const subscriptionMiddleware = (storeAPI) => {
let nextSubscriptionId = 0;
const subscriptions = {};
const bus = createBusSomehow();
return (next) => (action) => {
switch(action.type) {
case "CREATE_SUBSCRIPTION" : {
const {callback} = action;
const subscriptionId = nextSubscriptionId;
nextSubscriptionId++;
const subscription = bus.channel(PATH, { mode: bus.wsModes.PULL })
.createListener((...args) => {
callback(dispatch, getState, ...args);
});
subscriptions[subscriptionId] = subscription;
return subscriptionId;
}
case "CLOSE_SUBSCRIPTION" : {
const {subscriptionId} = action;
const subscription = subscriptions[subscriptionId];
if(subscription) {
subscription.close();
delete subscriptions[subscriptionId];
}
return;
}
}
}
}
// Use over in your components file
function createSubscription(callback) {
return {type : "CREATE_SUBSCRIPTION", callback };
}
function closeSubscription(subscriptionId) {
return {type : "CLOSE_SUBSCRIPTION", subscriptionId};
}
// and in your component:
const actionCreators = {createSubscription, closeSubscription};
class MyComponent extends React.Component {
componentDidMount() {
this.subscriptionId = this.props.createSubscription(this.onMessageReceived);
}
componentWillUnmount() {
this.props.closeSubscription(this.subscriptionId);
}
}
export default connect(null, actionCreators)(MyComponent);
I tried out your solution for my own problem which involves creating a socket instance only when a user is logged and here is how my code looks:
const socketMiddleWare = url => store => {
const socket = new SockJS(url, [], {
sessionId: () => custom_token_id
});
return next => action => {
switch (action.type) {
case types.USER_LOGGED_IN:
{
socket.onopen = e => {
console.log("Connection", e.type);
store.dispatch({
type: types.TOGGLE_SOCK_OPENING
});
if (e.type === "open") {
store.dispatch({
type: types.TOGGLE_SOCK_OPENED
});
createSession(custom_token_id, store);
const data = {
type: "GET_ACTIVE_SESSIONS",
JWT_TOKEN: Cookies.get("agentClientToken")
};
store.dispatch({
type: types.GET_ACTIVE_SESSIONS,
payload: data
});
}
};
socket.onclose = () => {
console.log("Connection closed");
store.dispatch({
type: types.POLL_ACTIVE_SESSIONS_STOP
});
// store.dispatch({ type: TOGGLE_SOCK_OPEN, payload: false });
};
socket.onmessage = e => {
console.log(e)
};
if (
action.type === types.SEND_SOCKET_MESSAGE
) {
socket.send(JSON.stringify(action.payload));
return;
} else if (action.type === types.USER_LOGGED_OUT) {
socket.close();
}
next(action);
}
default:
next(action);
break;
}
};
};
It doesn't work though but could you point me in the right direction. Thanks.

React Redux adding extra field for action cause promise to return differently

I want to add a isLoading flag to my action generator and reset it at my reducer. Initially without the flag, my code works and the action looks like the following
export function getList() {
const FIELD = '/comics'
let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH;
const request = axios.get(searchUrl)
return {
type: FETCH_LIST,
payload: request,
}
}
and reducer looks like
const INITIAL_STATE = { all: [], id: -1, isLoading: false };
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_COMIC_LIST:
console.log('reducer action =', action, 'state =', state)
return {
...state,
all: action.payload.data.data.results,
isLoading: false
}
default:
return state;
}
}
As you can see, the object returns fine and I can get my list via action.payload.data.data.results
Note that I am using redux promise as my middleware to handle the promise.
As soon as I changed my action to the following and re run the code I get my payload (as shown in image below) to be a Promise rather than the object that was returned
export function getComicList() {
const FIELD = '/comics'
let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH;
const request = axios.get(searchUrl)
return {
type: FETCH_COMIC_LIST,
isLoading: true,
payload: request,
}
}
Why is simply adding another variable causing this problem??
Redux Promise and Redux Promise Middleware are both compliant with Flux Standard Action (FSA). Add a meta key to the action and assign your key/value pairs within it.
Related question/answer: https://stackoverflow.com/a/35362148/4290174
From the FSA documentation:
The optional meta property MAY be any type of value. It is intended for any extra information that is not part of the payload.
Try this - its how its done using redux-thunk - so I'm hoping its similar to redux-promise-middleware. Also see what is returned from:
all: action.payload in your reducer
export function getList() {
const FIELD = '/comics'
let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH;
// return a promise that the middleware should handle
// return response or error from promise
return axios.get(url)
.then((response) => {
type: FETCH_LIST,
isLoading: true,
payload: response
}).error((response) => {
//handle error
});
}
I think the cleanest and most react-redux way is:
//types:
const FETCH_LIST_START = "…";
const FETCH_LIST_SUCCESS = "…";
const FETCH_LIST_ERROR = "…";
//action
const loadList = url => dispatch => {
dispatch({type: FETCH_LIST_START });
fetch(url)
.then(res => {
if (res.status !== 200) throw new Error('load failed');
dispatch({
type: FETCH_LIST_SUCCESS,
payload : { res } }
})
)
.catch(err => dispatch({
type: FETCH_LIST_ERROR,
payload: { error } })
);
};
//REDUCER
case: FETCH_LIST_START:
return Object.assign({}, state, { isLoading: true });
break;
case: FETCH_LIST_SUCCESS:
return Object.assign({}, state, {
isLoading: false,
data: action.payload.res
});
break;
case FETCH_LIST_ERROR:
…
break;
So that assumes you are using redux-thunk. The basic Idea is to make your reducer handle the state, including setting the isLoading thing, that way, you can update your component while the request state changes… The code above is not copy/paste ready, it is meant to transport the idea.

Resources