Redux: Call the same api but dispatch different actions - javascript

Currently I have a redux actions which is getUsers() to get all users.
export function getUsers (params) {
return async dispatch => {
await dispatch({ type: 'GET_USERS_REQUEST' });
const urlParams = params ? new URLSearchParams(Object.entries(params)) : null;
return axios({
method: 'get',
url: Environment.GET_USERS+ `?${urlParams}`,
headers: { 'Content-Type': 'application/json' },
}).then (async res => {
await dispatch({ type: 'GET_USERS_SUCCESS', allUsers: res.data.Data });
}).catch (err => {
dispatch({ type: 'GET_USERS_FAILURE', error: err.toString() });
});
}
}
Now I want to use the same getUsers() but with param id (eg. getUsers({ UserId: 'jamesbond007' })),
and in the action I want to dispatch({ type: 'GET_USER_BY_ID_SUCCESS', user: res.data.Data })
How can I dispatch different actions with the same api call? Should I duplicate the same code but change the action dispatch? If doing so it becomes repetitive function.

You can decide the action by param id, like
// all users, paramId is null
// a user, paramId is xxxx
const action = paramId ?
{ type: 'GET_USER_BY_ID_SUCCESS', user: res.data.Data } :
{ type: 'GET_USERS_SUCCESS', allUsers: res.data.Data };
dispatch(action);
But, I think your idea is not good. It is better to do the logic in 2 methods. It is readable.

I found way to do this. I declare variables REQUEST, SUCCESS, FAILURE and check if the param object contains UserId. If yes, it will dispatch GET_USER_BY_ID instead of GET_USERS.
export function getUsers (params) {
let REQUEST, SUCCESS, FAILURE;
if (!_.isEmpty(params) && params.UserId) {
REQUEST = 'GET_USER_BY_ID_REQUEST';
SUCCESS = 'GET_USER_BY_ID_SUCCESS';
FAILURE = 'GET_USER_BY_ID_FAILURE';
} else {
REQUEST = 'GET_USERS_REQUEST';
SUCCESS = 'GET_USERS_SUCCESS';
FAILURE = 'GET_USERS_FAILURE';
}
return async dispatch => {
await dispatch({ type: REQUEST });
const urlParams = params ? new URLSearchParams(Object.entries(params)) : null;
return axios({
method: 'get',
url: Environment.GET_USERS+ `?${urlParams}`,
headers: { 'Content-Type': 'application/json' },
}).then (async res => {
await dispatch({ type: SUCCESS, payload: res.data.Data });
}).catch (err => {
dispatch({ type: FAILURE, error: err.toString() });
});
}
}
Note that in my reducer, I use only action.payload. Eg:
case 'GET_USERS_SUCCESS':
return { ...state, isFetching: false, userList: action.payload };
case 'GET_USER_BY_ID_SUCCESS':
return { ...state, isFetching: false, user: action.payload };

Related

React navigation auth flow using ternary operator throwing Typeerror

I have a simple auth stack as follow
export default () => {
const { state } = useContext(AuthContext);
return (
<AuthProvider>
<NavigationContainer>
{state.token ? <MainNavigator /> : <AuthNavigator />}
</NavigationContainer>
</AuthProvider>
);
};
The initial state of token is defined as null in the AuthContext folder, code below. But when running the program i get the following error TypeError: undefined is not an object (evaluating '_useContext.state')
const authReducer = (state, action) => {
switch (action.type) {
case "error":
return { ...state, errorMessage: action.payload };
case "signin":
return { errorMessage: "", token: action.payload };
default:
return state;
}
};
const tokencheck = (dispatch) => async () => {
const token = await AsyncStorage.getItem("token");
if (token) {
dispatch({ type: signin, payload: token });
navigate("Home");
} else {
navigate("SignIn");
}
};
const signup =
(dispatch) =>
async ({ username, password }) => {
try {
const response = await tracker({
method: "post",
url: "/user",
data: qs.stringify({
username: username,
password: password,
}),
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
await AsyncStorage.setItem("token", response.data.email);
// dispatch({ type: "signin", payload: response.data.access_token });
navigate("SignIn");
} catch (err) {
dispatch({
type: "error",
payload: "Something's not write, plz try again",
});
console.log(err);
}
};
const signin =
(dispatch) =>
async ({ username, password }) => {
try {
const response = await tracker({
method: "post",
url: "/login",
data: qs.stringify({
username: username,
password: password,
}),
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
await AsyncStorage.setItem("token", response.data.access_token);
dispatch({ type: "signin", payload: response.data.access_token });
navigate("Home");
} catch (err) {
console.log(err);
dispatch({
type: "error",
payload: "Start debuggin",
});
}
};
const signout = (dispatch) => {
return () => {};
};
export const { Provider, Context } = creatingContext(
authReducer,
{ signin, signout, signup, tokencheck },
{ token: null, errorMessage: "" }
);
The ternary logic is sound and I have defined the initial state then why is this error persisting.
While I was looking for state in token what i should look for is value.
The problem is simply solved with
{state.token != null : <nav/>?<auth/>}

Req.body from the database is returning empty object when trying to delete user

I'm trying to delete an user, but the problem is that I'm trying to do that by taking the user id from the req.body in the database, I have the following logic in the redux actions and I think here is a problem with the order of the params:
export const deleteUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_DELETE_REQUEST, payload: userId });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.delete(
"http://localhost:3030/v1/user/userProfile/deleteUser",
{
userId
},
{
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: USER_DELETE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: USER_DELETE_FAIL, payload: message });
}
};
When I'm doing console logging the req.body in the backend is empty, what can I do?

Deleting a user is returning nothing in the backend

I'm trying to delete an user, but the req.body in the backend is an empty object.
In the backend I have the following code:
const deleteUser = async (req, res) => {
console.log(req.body);
console.log(req.config);
const user = await User.findById(req.body.userId);
if (user) {
const deleteUser = await user.remove();
res.send({ message: "User Deleted", user: deleteUser });
} else {
res.status(404).send({ message: "User Not Found" });
}
};
Here the console log is an empty object, I must that the other functions work perfectly.
In the frontend, I'm using redux, I think I'm doing something wrong in the actions, but I can't find out what, I will post all my code for reference.
action.js:
export const deleteUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_DELETE_REQUEST, payload: userId });
try {
const { data } = await Axios.delete(
"http://localhost:3030/v1/user/userProfile/deleteUser",
{
userId: userId,
}
);
dispatch({ type: USER_DELETE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: USER_DELETE_FAIL, payload: message });
}
};
In the reducer:
export const userDeleteReducer = (state = {}, action) => {
switch (action.type) {
case USER_DELETE_REQUEST:
return { loading: true };
case USER_DELETE_SUCCESS:
return { loading: false, success: true };
case USER_DELETE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
And I'm calling the action like that:
const userSignin = useSelector((state) => state.userSignin);
const { userInfo, loading, error } = userSignin;
<button
onClick={() => {
console.log(userInfo._id);
dispatch(deleteUser(userInfo._id));
props.onClose();
}}
className='deleteAccountModalButton'
>
Delete account!
</button>
I tried everything, but I can't find where the problem, can somebody tell me why the req.body is empty in the backend?
EDIT:
I managed to make it work by modifying the order of parameters in actions:
export const deleteUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_DELETE_REQUEST, payload: userId });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.delete(
"http://localhost:3030/v1/user/userProfile/deleteUser",
{
data: {
headers: { Authorization: `Bearer ${userInfo.token}` },
userId,
},
}
);
dispatch({ type: USER_DELETE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: USER_DELETE_FAIL, payload: message });
}
};
I will leave this here in case somebody else will have this problem.

Nested axios calls updating react state before all requests have finished

I am doing some nested axios calls as in order to create the object i want i need to fire off a couple of different API's. My problem is that when i call setState, the state will start updating before all requests have finished so my table will populate entry by entry which does not look nice.
here is my code:
fetchServices = async ()=> {
this.setState({isLoading: true})
await axios({
method: 'get',
url :getApiUrl(),
headers:{
"Accept": "application/json"
}
})
.then( async response => {
let Data: any[] = []
response.data.Message.forEach(async (e: any) => {
await axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn + "/services",
headers:{
"Accept": "application/json"
}
}).then( async response => {
console.log(response)
if(response.data.Message !== "No services found"){
response.data.Message.forEach(async(e:any)=>{
let orgName = await axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn,
headers:{"Accept": "application/json"}})
.then(response => {return response.data.Message.organisationName});
let entry = {
servicename: { text: e.serviceName },
servicetype: { text: e.serviceTypeDescription },
organisation: { text: orgName },
};
Data.push(entry);
this.setState({ tableData: Data });
})
};
});
});
setTimeout(()=>{this.setState({isLoading: false})}, 100)
}).catch(error => {
alert(error)
});
};
Well you are pushing your data one by one. You can use Promise.all to await all requests complete then populate your state.
if (response.data.Message !== "No services found") {
const promises = response.data.Message.map((e: any) => {
axios({
method: 'get',
url: getApiUrl() + "/" + e.organisationErn,
headers: {
"Accept": "application/json"
}
})
.then(response => {
return response.data.Message.organisationName
})
.then(org => {
return {
servicename: {
text: e.serviceName
},
servicetype: {
text: e.serviceTypeDescription
},
organisation: {
text: org
},
};
});
});
const Data = await Promise.all([...promises]);
this.setState({ tableData: Data });
BTW there may be some parentheses errors.
I think its better to get the result from each axios request and use it at the end, instead of using then
like
const result1= await axios.get(...);
const result1= await axios.get(...);
const result1= await axios.get(...);
setState({ tableData: Data });
you can also refer https://medium.com/better-programming/how-to-use-async-await-with-axios-in-react-e07daac2905f

this.props.history.push does not redirect on first attempt

We are trying to redirect to a search results page once we have our results. We are using axios.get() to send the get request which follows:
export const searchPosts = (type, query) => async dispatch => {
dispatch({ type: PARTIAL_LOADING, payload: false });
const res = await axios.get('/api/search/',
{
params: {
type: type,
query: query
}});
dispatch({ type: PARTIAL_LOADING, payload: true });
dispatch({
type: SEARCH,
payload: res.data
});
};
We are trying to redirect to the next page using the following:
async submitSearch(values) {
const type = values.type ? values.type : "Default";
const query = values.query;
if (type && query)
{
await this.props.searchPosts(type, query)
.then(_ => {
this.props.history.push('/search_results');
});
}
}
This redirects after the first attempt but will always fail the first time.

Categories

Resources