Async Actions resolve before fetch result is retrieved - reactjs

I'm using Redux with redux-thunk to retrieve categories from an API. I have an action called viewCategory that depends on having categories in the store state.
I used the example of fetching Reddit posts from the Redux site:
https://redux.js.org/advanced/asyncactions#actions-js-asynchronous
The problem I have is that when I call viewCategory the promise thinks it's resolved when REQUEST_CATEGORIES is dispatched and not RECEIVE_CATEGORIES. So if log my state in the then statement I have an
empty list of categories.
export function viewCategory(urlKey) {
return (dispatch, getState) => {
dispatch(fetchCategoriesIfNeeded()).then(() => {
let state = getState();
console.log(state); // should have categories
let categories = [...state.categories.mainCategories,
...state.categories.specialCategories];
let matchCategory = categories.find((category) => {
return category.custom_attributes.find(x => x.attribute_code === "url_key").value === urlKey;
});
dispatch({
type: Categories.VIEW_CATEGORY,
category: matchCategory
});
});
};
}
The functions that decide if categories should be fetched at all:
function shouldFetchCategories(state) {
const categories = state.categories;
if(categories.isFetching || categories.mainCategories.length > 0) {
return false;
} else {
return true;
}
}
export function fetchCategoriesIfNeeded() {
return (dispatch, getState) => {
if(shouldFetchCategories(getState())) {
return dispatch(fetchCategories());
} else {
return Promise.resolve();
}
};
}
The function that contains the actual fetch call:
function fetchCategories() {
return (dispatch, getState) => {
dispatch(requestCategories());
const {locale} = getState().settings;
return fetch(`${BASE_URL}/categories/list`, {
method: "POST",
headers: {
"Accept-Language": locale
},
body: "Not interesting for stackoverflow"
})
.then(response => response.json())
.then(json => {
if(json !== undefined && json.items){
dispatch(receiveCategories(json.items));
}
});
};
}
The functions where I dispatch types REQUEST_CATEGORIES & RECEIVE_CATEGORIES:
function requestCategories() {
return {
type: Categories.REQUEST_CATEGORIES
};
}
function receiveCategories(result) {
const mainCategories = result.filter(category => category.level === 2);
const subCategories = result.filter(category => category.level === 3);
const categories = mainCategories.map(category => {
let children = subCategories.filter(x => x.parent_id === category.id);
return {
...category,
children
};
});
let specialCategories = categories.splice(categories.length - 2, 2);
return {
type: Categories.RECEIVE_CATEGORIES,
categories: categories,
specialCategories: specialCategories,
receivedAt: Date.now()
};
}
Any idea what I am doing wrong here? If you need any extra code or information please let me know.

Related

HTTP put and get(id) request ReactQuery

I change the redux in my project to ReactQuery,and i got some problem with put req in my code.
this is my code
const { dispatch } = store;
const editClientDataAsync = async ( id,data ) => {
await axiosObj().put(`clients/${id}`,data);
}
const { mutateAsync: editClientData, isLoading } = useMutation(['editClientData'], editClientDataAsync, {
onSuccess: () => dispatch({ type: SUCCESS_DATA, payload: { message: "Success" } }),
onError: () => dispatch({ type: ERROR_DATA, payload: { message: "Error" } })
});
return { editClientData, isLoading }
}
same problem with when i try to get some req with id
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async ({ queryKey }) => {
const [_, { id }] = queryKey;
console.log(queryKey)
if (!id)
return;
const { data } = await axiosObj().get(`clients/${id}`)
console.log(data)
return data;
}
const { data: clientDetails, isLoading } = useQuery(['ClientId', { id }], getClientDetails)
return { clientDetails, isLoading }
Mutation functions only take 1 argument
Check where you use the editClientData mutation and pass the arguments in one object.
const editClientDataAsync = async ({ id, data }) => {
await axiosObj().put(`clients/${id}`,data);
}
return useMutation(['editClientData'], editClientDataAsync, ...);
Are you sure you get an id passed to the function?
You can disable the query until you get that id with the enabled option, so you don't make an unnecessary http call.
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async (id) => {
const { data } = await axiosObj().get(`clients/${id}`)
return data;
}
return useQuery(['client', id], () => getClientDetails(id), { enabled: !!id })
Disable/pausing queries

Redux state updated but component not re-rendered (while using promise)

I am using React/Redux.
The main issue is that when i use Promise then component is not re-rendered, whereas the code is working fine when promise code is not used.
Action Creator
const updateColor = colorobj => {
return dispatch =>
new Promise(function(resolve, reject) {
dispatch(fetchColorBegin());
axios
.post(config.APIURL.color.update, colorobj)
.then(response => {
const data = response.data;
if (data.errorno !== 0) {
dispatch(fetchColorFailure(data.errormsg));
reject(data.errormsg);
} else {
dispatch(updateColorSuccess(colorobj));
resolve('Color Updated');
}
})
.catch(error => {
dispatch(fetchColorFailure(error.message));
reject(error.message);
});
});
};
Reducer
case UPDATE_COLOR_SUCCESS:
const todoIndex = state.data.findIndex(todo => todo.id === action.payload.id);
return update(state, {
loading: { $set: false },
data: { [todoIndex]: { $merge: action.payload } },
error: { $set: null}
});
Component
the state is updated but the component is not updated.
const handleEditOk = values => {
let colorobj = {
id: state.updateData.id,
colorname: values.colorname,
colorcode: values.colorcode,
};
dispatch(updateColor(colorobj))
.then(response => {
message.success(response);
onCancel();
})
.catch(error => {
message.error(error);
});
};
The component update itself only on commenting the promise code.
The problem now is that it is not showing success/failure message.
const handleEditOk = values => {
let colorobj = {
id: state.updateData.id,
colorname: values.colorname,
colorcode: values.colorcode,
};
dispatch(updateColor(colorobj))
// .then(response => {
// message.success(response);
// onCancel();
// })
// .catch(error => {
// message.error(error);
// });
};
Kindly suggest.

Dispatch multiples http request React/Redux

I'm trying to dispatch more than one axios request inside my method. However, it is not working.
export const getImages = (res) => {
return {
type: actionTypes.GET_IMAGES,
payload: res
}
}
export const loadImages = (imgs, cId) => {
return dispatch => {
let data = [];
for(const i of imgs) {
const id = i.id;
axios.get(`${api.URL}/test/${cId}/files/${id}`)
.then(res => {
if(res.data !== -1) {
const obj = {
name: res.data,
desc: i.caption
};
data(obj);
}
//dispatch(getImages(data));
});
}
console.log('Action:');
console.log(data);
dispatch(getImages(data));
}
}
The console log does not print anything. Do I need to dispatch inside the .then()? If so, how can I run multiples requests before dispatching?
Thanks

Trying to modify a data from a React Promise Response changes globally

I have created a codesandbox with a simplified version of my problem
https://codesandbox.io/s/new-react-context-api-ei92k
I get something from a fetch (in this case a user)
I then create a local copy of this user and make some changes to it
The problem: Any changes update my initial user object
Can someone tell me how this is possible? and how can I avoid this?
import React, { useState, useEffect } from "react";
import { AppSessionContext } from "./AppContext";
import Header from "./Header";
const user = {
userName: "jsmith",
firstName: "John",
lastName: "Smith",
isAdmin: true
};
const loadProfile = () => Promise.resolve(user);
function createUserWithNewName(userToUpdate) {
userToUpdate["userName"] = "Dummy";
return userToUpdate;
}
const App = () => {
const [user, setUser] = useState({});
const [Loaded, setLoaded] = useState(false);
var amendedUser = {};
useEffect(() => {
loadProfile()
.then(user => {
setUser(user);
console.log(user);
})
.then(() => {
amendedUser = createUserWithNewName(user);
console.log(amendedUser);
console.log(user);
})
.then(setLoaded(true));
}, []);
if (!Loaded) {
return "Loading";
}
return (
<AppSessionContext.Provider value={{ user }}>
<div className="App">
<Header />
</div>
</AppSessionContext.Provider>
);
};
export default App;
snippet of production code
loadTableDefault() {
fetch(defaultUrl(), {method: 'GET'})
.then(res => res.json())
.then(response => {
this.setState({
data: response,
})
return response
})
.then(response => {
this.setState({
table_data: formatResponsePretty(response),
})
})
.catch(error => console.error('Error:', error));
}
formatResponsePretty
export function formatResponsePretty(oldData) {
const newData = {
...oldData,
};
// consider re-writting the flask response to this format
const obj = { allocations: [] };
var theRemovedElement = ''
var ports = []
ports = Object.values(newData['allocations']['columns']);
ports.shift();
var dataArray = ['allocations', 'conditions', 'liquidity', 'hedging']
for (const index of dataArray) {
for (const i of newData[index]['data']) {
theRemovedElement = i.shift();
if (index === 'allocations') {
obj[index][theRemovedElement] = i
}
else {
obj[theRemovedElement] = i;
}
}
}
const rows = []
let index = 0;
Object.keys(obj).forEach(element => {
index = formatting.findIndex(x => x.name === element)
if (formatting[index] && formatting[index]['type'] === 'number') {
var new_obj = obj[element].map(function (el) {
return Number(el * formatting[index]['multiplier']).toFixed(formatting[index]['decimal']) + formatting[index]['symbol']
})
rows.push(new_obj)
}
else if (formatting[index] && formatting[index]['type'] === 'string') {
rows.push(obj[element])
}
else if (formatting[index] && formatting[index]['type'] === 'boolean') {
// there should be logic here to display true or false instead of 1 and 0
// this could be in the upload
rows.push(obj[element])
}
else {
rows.push(obj[element])
}
})
const arrOfObj = createRecords(ports, rows)
return {obj: obj, ports: ports, rows: rows, arrOfObj: arrOfObj}
}
In createUserWithNewName() you are updating the original user object and returning it.
You instead want to create a new object with all the old user properties, but with just the username changed. Thankfully, object destructuring makes this super easy:
function createUserWithNewName(oldUser) {
const newUser = {
...oldUser,
userName: 'Dummy',
};
return newUser;
}
This will copy all the properties of oldUser to a new object and then just update userName!
You're also going to want to pass user down to that second .then() as it won't currently be available in there:
.then(user => {
setUser(user);
console.log(user);
return user;
})
.then(user => {
amendedUser = createUserWithNewName(user);
console.log(user, amendedUser);
})
Update CodeSandbox link: https://codesandbox.io/s/new-react-context-api-tgqi3

Can't return response from redux-thunk

I'm calling an action from a component:
this.props.createWebsite(this.state)
This calls an action and passes in some state. The action looks like this:
export const createWebsite = data => {
return (dispatch, getState) => {
return axios.post(
API.path + 'website/',
{
// some data
}
)
.then(response => {
})
.catch(error => {
})
}
}
I want to handle the response and error in the component that called this, rather than in the action itself. How can I do this? I have tried:
this.props.createWebsite(this.state).then(response => { /**/ }).catch(error => { /**/ })
This sort of works but it doesn't catch errors.
You need to remove the catch from the createWebsite declaration.
It handle the error and to not propagate it. So the error is lost.
To get it :
remove the catch
export const createWebsite = data => {
return (dispatch, getState) => {
return axios.post(
API.path + 'website/',
{
// some data
}
)
.then(response => {
return response;
})
}
}
rethrow the exception
export const createWebsite = data => {
return (dispatch, getState) => {
return axios.post(
API.path + 'website/',
{
// some data
}
)
.then(response => {
return response;
})
.catch(error => {
// Do something
throw error;
})
}
}

Resources