How to pass status codes to client side? (Redux asyncThunk) - reactjs

I want to access the status sent from my Express server in my React component. I am using redux to dispatch a login fetch request and want to conditionally render different childrens based on the status code received (eg. 'Incorrect Password' if status code 401)
loginmodal.js
const handleLogin = async (event) => {
event.preventDefault();
let result = await dispatch(login({ email, password }))
console.log(result) //payload is undefined if incorrect password.
}
userAuthSlice.js (Redux)
export const login = createAsyncThunk(
'userAuth/login',
async (payload, thunkAPI) => {
const { email, password } = payload
const result = await fetch(
loginPath, {
mode: 'cors',
credentials: 'include',
method: 'post',
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
},
}
)
console.log(result) // Response {type: 'cors', url: 'http://localhost:5000/login', redirected: false, status: 401, ok:
const response = await result.json()
console.log(response)
return response
}
)
const userAuthSlice = createSlice({
extraReducers: {
[login.pending]: (state) => {
state.isLoading = true
},
[login.fulfilled]: (state, action) => {
state.isLoading = false
state.isAuth = true
},
[login.rejected]: (state) => {
state.isLoading = false
},
}
server.js
app.post('/login', (req, res) => {
const email = req.body.email;
const plainTextPassword = req.body.password;
User.find({ email: email }).limit(1).exec(function (err, existingUser) {
if (existingUser.length === 0) {
res.sendStatus(401)
} else {
bcrypt.compare(plainTextPassword, existingUser[0].password, function (err, response) {
if (response === true) {
req.session.user = existingUser[0]._id
res.json(req.session)
} else {
res.sendStatus(401)
}
})
}
}
)
})
In my loginModal, i console.log the payload from dispatch. My payload is undefined if my promise is rejected from incorrect password. My payload includes the status code if its fulfilled eg correct password. How can i get the status code / payload, even if the promise is rejected? Or is there another approach to this problem?

You need to check the Response.ok and / or Response.status properties to determine if your request was successful.
If they are not, I would recommend returning a rejected value. The rejected object can have the properties you need like status
const res = await fetch(...);
if (!res.ok) {
// thunkApi.rejectWithValue
return rejectWithValue({
status: res.status,
message: await res.text(),
});
}
return res.json();
Then you can check for rejected promises and refer to the status in your consuming code
const handleLogin = async (event) => {
event.preventDefault();
try {
// needs the `.unwrap()` function call
const result = await dispatch(login({ email, password })).unwrap();
console.log(result);
} catch (err) {
console.warn(err);
switch (err.status) {
case 401:
// do something for 401
break;
default:
// default error handling
}
}
}

Related

how to call function after async task react

i want to navigate to dashboard after login and dashboard is protected route
const handleLogin = (e) => {
e.preventDefault();
if (email || password) {
dispatch(loginUser({ email, password }));
navigate("/dashboard");
} else {
toast.error("Please Enter Email and Password");
}
};
i am using redux toolkit createAsyncThunk for api request
export const loginUser = createAsyncThunk("auth/login", async (userDetails) => {
try {
const { email, password } = userDetails;
const res = await fetch("http://localhost:5000/api/users/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const result = await res.json();
if (result.error) {
toast.error(result.error);
} else {
toast.success("Login successfull");
localStorage.setItem("user", JSON.stringify(result));
return result;
}
} catch (error) {
console.log(error);
}
});
when i click on login it try to navigate the page before the state update what i want the navigate function wait untill the api respone recieve then navigate to dashboard
dispatch(loginUser({ email, password })); returns a promise, you can wait for the promise to resolve before doing additional work:
const handleLogin = () => {
dispatch(loginUser({ email, password })).then(() => {
navigate("/dashboard");
})
}
see Unwrapping Result Actions

Generic function to request api with Axios

I am trying to build a generic function for my endpoints, using Axios and React. Generic because I have always the same header and I do not want to repeat a lot of code for each of my components.
To do that, I built this function (sorry, a lot of comments that I will remove after of course) :
export const getRequest = ( endpoint ) => axios
.get( env._URL_SERVER_ + endpoint, { headers: getHeaders() } )
.then((res) => {
// Success
console.log(res);
return {error: false, response: res.data};
})
.catch((error) => {
// Error
if (error.response) {
/*
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
console.log(error.response.data);
console.log(error.response.status);
return {error: true, status: error.response.status, data: error.response.data};
} else if (error.request) {
/*
* The request was made but no response was received, `error.request`
* is an instance of XMLHttpRequest in the browser and an instance
* of http.ClientRequest in Node.js
*/
console.log(error.request);
return {error: true, data: error.request };
} else {
// Something happened in setting up the request and triggered an Error
console.log('Error', error.message);
return {error: true, data: error.message}
}
});
Ant then in my components I do that :
getSchools = () => {
this.setState({
loadingSchools: true
}, () => {
getRequest(`/schools?name=${this.state.filterByName}&city=${this.state.filterByCity}&school_type_id=${this.state.filterBySchoolTypeId}&page=${this.state.selectedPage}`)
.then((response) => {
// there is an error
if (!response.error) {
this.setState({
schools: response.response.data,
meta: response.response.meta,
links: response.response.links
})
} else {
this.setState({
error: true,
errorMessage: response.data,
})
}
})
.then(() => {
this.setState({loadingSchools : false});
})
})
}
It works fine. I tested it in several situation (all is OK - 200, not found - 404, no response). But is it a good practice ? I feel that there is a lot of codes in the parent component. Maybe I complicate my life?
Here is how I've done it:
var URL_BACKEND = "http://localhost:5000/";
// Create Function to handle requests from the backend
callToBackend = async (ENDPOINT, METHOD) => {
const options = {
url: `${URL_BACKEND}${ENDPOINT}`,
method: METHOD,
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
};
const response = await axios(options);
return response.data;
}
// Then you make a call with the exact endpoint and method:
const response = await this.callToBackend('createSetupIntent', 'POST');
console.log(JSON.stringify(response));
create one common file for base URL let's say api.js
// api.js file code
export const apiUrl = axios.create({
baseURL: 'http://localhost:5000',
});
Register file
// register.js file code
import { apiUrl } from './api';
try {
const resp = await apiUrl.post('/api/register', {
username,
email,
password,
});
const { data, status } = resp;
if (Object.keys(data).length && status === 200) {
// received api data successfully
console.log('API response', data);
}
} catch (err) {
console.log(err);
}
// For auth request
try {
const token = localstorage.getItem('token');
const res = await apiUrl.post(
'/authroute',
{
name: fullName,
originCountry: country,
career: careerStatus,
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
const { data, status } = strapiRes;
if (Object.keys(data).length && status === 200) {
return res.status(status).json(data);
}
} catch (error) {
throw new Error(error);
}
// same for all request
apiUrl.get(endpoint);
apiUrl.post(endpoint, body);
apiUrl.put(endpoint, body);
apiUrl.delete(endpoint, body);

react & redux with hooks: Actions must be plain objects. Use custom middleware for async actions

i tried looking for similar answers to help solve my problem but i couldn't find anything using react redux hooks. This code was from a tutorial and originally written using the Context api. I wanted to trying using it with react-redux-hooks, but i got stuck. Basically i'm trying to register a user with a name, email and password, then pass these three as an object to the express server which will validated it and give me back a jwt token. Then come back to the client side and send the token to the reducer, which adds the token to localstorage and sets the state to isAuthenticated. The error i get is on the dispatch.
Dispatch
const onSubmit = e => {
e.preventDefault();
if (name === "" || email === "" || password === "") {
dispatch(setAlert("Please enter all fields", "danger"));
} else if (password !== password2) {
dispatch(setAlert("Passwords do not match", "danger"));
} else {
dispatch(register({ name, email, password })); // Error is here
}
setTimeout(() => {
dispatch(removeAlert());
}, 5000);
};
Action
export const register = async formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
try {
const res = await axios.post("/api/users", formData, config);
return {
type: "REGISTER_SUCCESS",
payload: res.data
};
} catch (err) {
return {
type: "REGISTER_FAIL",
payload: err.response.data.msg
};
}
};
Reducer
const authReducer = (
state = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
error: null
},
action
) => {
switch (action.type) {
case "REGISTER_SUCCESS":
console.log("register success");
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false
};
case "REGISTER_FAIL":
console.log("register failed");
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
error: action.payload
};
default:
return state;
}
};
Store
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const store = createStore(
allReducers,
composeEnhancers(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Express server
router.post(
"/",
[
check("name", "Please a name")
.not()
.isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
msg: "User already exists"
});
}
user = new User({
name,
email,
password
});
// hash passsword
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: 360000
},
(err, token) => {
if (err) throw err;
res.json({
token
});
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I believe this question has answers to the issue your experiencing here: how to async/await redux-thunk actions?
Using this example, it may look something like this (wasn't able to test it):
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
const request = axios.post("/api/users", formData, config);
return dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
request.then(onSuccess, onError);
};
};
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
return async dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
try {
const success = await axios.post("/api/users", formData, config);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
};

How to call ToastsStore.success or ToastsStore.error after API response in React component?

I created a component that contains the "New Article" form. The user can add a new article after clicking the Save button. The click event calls this.props.fetchAddPaper(data), which saves the article to the database.
If the response is 200, I would like to display information on the page for the user that the article has been successfully saved.
If the response is 500 or 400 or 401, I would like to display information that 'something went wrong try again'. To display alerts I use react-toasts. My question is: how can I get a response from the API after clicking the Save button so that you can display a success or error alert? How do I get a response from this.props.fetchAddPaper (data) in the handleSubmit method that I am calling?
Below is the fetchAddPaper that connects to the API. How do I get a response from such a method in a component?
const apiMiddleware = ({ dispatch }) => next => action => {
next(action);
if (action.type !== 'API')
return;
let {
url, // Endpoint address, relative to $HOST/api/
method, // http method (GET, POST, DELETE etc.)
params, // URI string params
data, // Post data
onSuccess, // Function accepting response. If redux action is returned, it will be dispatched
onFinish, // Function run on either success or error
onError, // Function accepting error
onValidationError, // Function accepting response with validation error
text, // Loading text. If not provided there will be no overlay while loading data
successText // Success text, shown on green bar. If not provided it won't be shown
} = action.payload;
// Allow for onSuccess, onFinish and onError to be either redux (and thunk) actions or normal functions
const conditionalDispatch = (action) =>
action && _.isFunction(action) ? dispatch(action) : action;
const request = {
headers: {
'Accept': 'application/json'
},
url: `${host}/api/${url}`,
method,
timeout: 180000
};
if (params) {
params = { ...params };
for (let prop in params) {
if (Array.isArray(params[prop])) {
const arrayData = arrayToGetParameters(params[prop], prop);
delete params[prop];
Object.assign(params, arrayData);
}
}
}
if (data) {
if (method.toUpperCase() === "GET" || method.toUpperCase() === "DELETE") {
throw new Error("Can't add request data to get or delete method");
}
request.headers['Content-Type'] = 'application/json;text/plain;text/json';
}
request.data = data;
request.params = params;
text && dispatch(onLoadingStart(text));
let notificationId = shortId.generate();
axios.request(request)
.then((response) => {
text && dispatch(onLoadingEnd());
onSuccess && dispatch(onSuccess(response.data));
onFinish && conditionalDispatch(onFinish);
if (successText) {
dispatch(onAddFlashMessage({type: 'success', text: successText, id: notificationId}));
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
}
})
.catch((error) => {
onFinish && conditionalDispatch(onFinish);
// onError && conditionalDispatch(onError(error));
onError && dispatch(onError(error));
dispatch(onLoadingEnd());
if (error.response && error.response.status === 401) {
//dispatch(onLogOut()); todo: wylogowanie
return;
}
if (error.response && error.response.status === 422 && onValidationError) {
conditionalDispatch(onValidationError(error));
}
else {
dispatch(onAddFlashMessage({...httpReqErrorHandler(error), id: notificationId}));
}
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
});
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ON_FETCH_ADD_PAPER:
return {
...state,
paper: action.response
};
default:
return state;
}
const onFetchAddPaper = (response) => ({ type: actionTypes.ON_FETCH_ADD_PAPER, response });
export const fetchAddPaper = (data) => {
return (dispatch) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => onFetchAddPaper(response),
onError: (error) => onFetchAddPaper(error)
}
});
};
};
handleSubmit(e) {
e.preventDefault();
let data = {
title: this.state.title,
header: this.state.header
}
this.props.fetchAddPaper(data);
console.log(this.props.paper);
//when the user first clicks the save button, the response is empty, but the second time the response has a value 200
}
function mapStateToProps(state) {
return {
paper: state.paper.paper
}
};
function mapDispatchToProps(dispatch) {
return {
fetchAddPaper: data => dispatch(fetchAddPaper(data))
}
}
//initialstore.jsx
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import apiMiddleware from './ApiMiddleware';
import rootReducers from '../RootReducers';
export default function initStore() {
const store = createStore(
rootReducers,
compose(
applyMiddleware(thunk, consoleMessages, apiMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
)
);
if (module.hot) {
module.hot.accept('../RootReducers', () => {
const nextRootReducer = require('../RootReducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
You can return a promise from your fetchAddPaper action
Something like this:
export const fetchAddPaper = (data) => {
return (dispatch) => {
return new Promise((resolve,reject) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => {
onFetchAddPaper(response);
resolve(response); //return your promise on success
},
onError: (error) => {
onFetchAddPaper(error);
reject(error); //return your promise on failure
}
}
});
})
};
};
So, whenever your action executes, it'll be a promise which you can then evaluate like -
this.props.fetchAddPaper(data).then(response => {..do something})

Fetch Post Request not returning payload but return status code (200)

So I am trying to create a user using redux-form. I have an express post route on the backend. NOTE: using redux-thunk for middleware, whatwg-fetch with webpack and babel-polyfill.
routes.post('/signup', async (req, res) => {
try {
const createdUser = await userController.createUser(req.body);
const JSONCreatedUser = JSON.stringify(createdUser);
res.json({
confirmation: 'success',
result: createdUser,
});
return JSONCreatedUser;
} catch (error) {
res.statusMessage = error.toString();
res.status(409).json({
confirmation: 'failure',
error: error.toString(),
});
}
});
So the problem I am having is that when I use postman. I will get the entire user object back.
But when I submit it using form I only get
Apimanager.js
export const signUserUpApi = async (url, params) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const { status, statusText } = response;
if (status === 409) {
throw new Error(statusText);
}
return response;
} catch (error) {
throw new Error(error.toString());
}
};
action.js
import constants from '../constants';
import { signUserUpApi } from '../utils/APIManager';
const signUserUpUrl = process.env.SIGN_USER_UP_URL || 'http://localhost:3000/user/signup';
export const signUserUp = (user) => {
return async (dispatch) => {
try {
const createdUser = await signUserUpApi(signUserUpUrl, user);
dispatch({
type: constants.SIGN_USER_UP,
user: createdUser,
});
return createdUser;
} catch (error) {
throw new Error(error);
}
};
};
export const signUserIn = (user) => {
return {
type: constants.SIGN_USER_UP,
user,
};
};
What I am trying to do is to get the User Object I created when I submit the form and redirect back to the page.
This is what I get back and it did create the user.
First thing, I need is why am I getting the https status code back and not the user object?
Second thing, what are the ways to redirect to the home page when a user successfully signed up logged in.

Resources