Getting response data from post request when using axios - reactjs

I'm trying to integrate the shippo api into a project that I am working on. I'm creating a post api request in my paymentActions.js document that is accessed on my PlaceOrderScreen.js. Currently, I am need to get the response sent from the api in my backend to my PlaceOrderScreen.js. In paymentActions.js, data from dispatch({ type: PAYMENT_SUCCESS, payload: data }) is returning my necessary response. How can I get this response in my PlaceOrderScreen.js? I was going to try to use useEffect(), but it was giving me an error when I was trying to put that within const handleSubmit.... I would really appreciate any help or advice. Thank you!
paymentActions.js
import Axios from 'axios';
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
import { BASE_URL } from '../constants/app.constants';
export const paymentInfo = (paymentMethodType, currency, userId) => async (dispatch) => {
dispatch({ type: PAYMENT_REQUEST, payload: paymentMethodType, currency, userId});
try {
const { data } = await Axios.post( 'api/stripe/pay', {
paymentMethodType,
currency,
userId,
})
dispatch({ type: PAYMENT_SUCCESS, payload: data })
}
catch (error) {
dispatch({
type: PAYMENT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
I need to get data from dispatch({ type: PAYMENT_SUCCESS, payload: data }) (the response) within:
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
const paymentMethodType = 'card';
const currency = 'usd';
const {error: backendError, clientSecret} = dispatch(paymentInfo(paymentMethodType, currency, userId));
if (backendError) {
//addMessage(backendError.message);
console.log(backendError)
return;
}
console.log('Client secret returned')
console.log(clientSecret)
const {error: stripeError, paymentIntent} = await stripe.confirmCardPayment(clientSecret,
{
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Jenny Rosen',
},
},
},
);
};
in my PlaceOrderScreen.js
paymentReducers.js
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
export const paymentInfoReducer = (state = {info:[]}, action) => {
switch (action.type) {
case PAYMENT_REQUEST:
return { ...state, loading: true };
case PAYMENT_SUCCESS:
return { ...state, loading: false, info: action.payload };
case PAYMENT_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};

Inside your reducer, you will add a key in the PAYMENT_REQUEST case. here we got the data that will come back from that request. That's what you want. then you will use it similar to your usage of the info and error key inside the PAYMENT_SUCCESS and PAYMENT_FAIL case.
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
export const paymentInfoReducer = (state = {info:[]}, action) => {
switch (action.type) {
case PAYMENT_REQUEST:
return { ...state, loading: true, success: action.payload };
case PAYMENT_SUCCESS:
return { ...state, loading: false, info: action.payload };
case PAYMENT_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};

Related

Redux store cannot update

I have a button trigger project create. What I want to do is when I click button, it will create a new project, and if there is no issue with product, it will go to the next step.
The dispatch on the click handler is working, I can see the redux runs the faild reducer if there is error, but the projectData.successStatus inside the handler cannot get the latest value which the the successStatus I want it to be false. It's still the previsous retrive project list success Status. So the nextStep() condition is not working.
Can someone help me find what's wrong?
This is the handler button:
const handleNextButton = useCallback(() => {
if (newProjectName) {
const newProjectWithProjectName = {
...newProject,
projectName: newProjectName,
}
dispatch(createNewProjectReq(newProjectWithProjectName)) // create new project
if (projectData.successStatus) {
nextStep()
}
}
}, [newProjectName, projectData])
On the action, I have request, add, fail:
export const createNewProjectReq = (newProject) => async (dispatch) => {
dispatch({ type: PROJECT_SENDING_REQUEST })
try {
const result = await createNewProject(newProject)
const { project, message } = result.data.data
dispatch({
type: PROJECT_LIST_ADD,
payload: { project, message },
})
} catch (error) {
dispatch({ type: PROJECT_REQUEST_FAIL, payload: error.data.message })
}
}
Reducer switch:
switch (action.type) {
case PROJECT_SENDING_REQUEST:
console.log("PROJECT_SENDING_REQUEST")
return {
...state,
loading: true,
successStatus: false,
}
case PROJECT_LIST_SUCCESS:
console.log("PROJECT_LIST_SUCCESS")
return {
loading: false,
projects: action.payload.projectListGroupByProjectId,
successStatus: true,
message: action.payload.message,
}
case PROJECT_LIST_ADD:
console.log("PROJECT_LIST_ADD")
return {
...state,
loading: false,
projects: [...state.projects, action.payload.project],
successStatus: true,
message: action.payload.message,
}
case PROJECT_REQUEST_FAIL: {
console.log("PROJECT_REQUEST_FAIL")
return {
...state,
loading: false,
successStatus: false,
message: action.payload,
}
}
default:
return state
}
Issues
handleNextButton callback is synchronous code.
Reducer functions are also synchronous code.
State is generally considered const during a render cycle, i.e. it won't ever change in the middle of a render cycle or synchronous code execution.
Because of these reason the projectData state will not have been updated yet, the conditional check happens before the actions are processed.
Solution
Since you really are just interested in the success of the action so you can go to the next step you can return a boolean value from the asynchronous action and await it or Promise-chain from it.
export const createNewProjectReq = (newProject) => async (dispatch) => {
dispatch({ type: PROJECT_SENDING_REQUEST });
try {
const result = await createNewProject(newProject);
const { project, message } = result.data.data;
dispatch({
type: PROJECT_LIST_ADD,
payload: { project, message },
});
return true; // <-- success status
} catch (error) {
dispatch({ type: PROJECT_REQUEST_FAIL, payload: error.data.message });
return false; // <-- failure status
}
}
...
const handleNextButton = useCallback(() => {
if (newProjectName) {
const newProjectWithProjectName = {
...newProject,
projectName: newProjectName,
}
dispatch(createNewProjectReq(newProjectWithProjectName))
.then(success => {
if (success) {
nextStep();
}
});
}
}, [newProjectName, projectData]);
Demo
Simple demo with click handler calling asynchronous function with 50% chance to succeed/fail.

Redux Action not returning JSON?

I'm struggling to figure out why my redux action is not returning the JSON from the GET request, even though when I submit the GET request in Postman, I can access the information?
The error I have returning is: Profile Not Found. Yet, like I said when I do the Postman request, it's working fine.
This Redux Action doesn't work:
// Get profile by id for admins
export const getUserProfile = (id) => dispatch => {
dispatch(setProfileLoading());
axios.get(`/admin/profile/${id}`)
.then(res =>
dispatch({
type: GET_PROFILE,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Here is the Admin route which works in Postman and is returning the JSON?
router.get('/admin/profile/:id', passport.authenticate('jwt', {
session: false
}), (req, res) => {
const errors = {};
User.findOne({
user: req.user.id
})
.then(user => {
if (req.user.role === 'admin') {
Profile.findById(req.params.id)
.then(profile => {
res.json(profile);
})
.catch(err => res.status(404).json({
profilenotfound: 'Profile not found'
}));
} else {
res.json({unauthorized: 'User is unauthorized to view this area'})
}
})
.catch(err => res.status(404).json(err));
});
Here is the reducer:
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE, GET_PROFILES }
from '../actions/types';
const initialState = {
profile: null,
profiles: null,
loading: false
}
export default function(state = initialState, action) {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case GET_PROFILES:
return {
...state,
profiles: action.payload,
loading: false
}
case CLEAR_CURRENT_PROFILE:
return {
...state,
profile: null
}
default:
return state;
}
}
As mentionned in the comments the problem is that you are not passing the id, you need to pass the id when you call your Redux action in your component for example if you call your getUserProfile method it should be something like that:
componentDidMount() {
const {getUserProfile} = this.props; // This is destructuring for better readability
// Here you need to pass your id for example 1234 or get it from params or from wherever you want...
getUserProfile(1234);
}

How do I avoid using separate _PENDING _FULFILLED and _REJECTED actions with redux thunk?

I am writing my actions and reducers with thunks that dispatch _PENDING, _FULFILLED, and _REJECTED actions. However, I am wanting a better solution to avoid the boilerplate. I am migrating to Typescript which doubles this boilerplate by requiring an interface for each _PENDING, _FULFILLED, and _REJECTED action. It is just getting out of hand. Is there a way to get the same/similar functionality of my code without having three action types per thunk?
localUserReducer.js
const initialState = {
fetching: false,
fetched: false,
user: undefined,
errors: undefined,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'GET_USER_PENDING':
return {
...state,
fetching: true,
};
case 'GET_USER_FULFILLED':
return {
...state,
fetching: false,
fetched: true,
user: action.payload,
};
case 'GET_USER_REJECTED':
return {
...state,
fetching: false,
errors: action.payload,
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'GET_USER_PENDING' });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'GET_USER_FULFILLED', payload: data });
} catch (err) {
dispatch({ type: 'GET_USER_REJECTED', payload: err.response.data });
}
};
I may have a huge misunderstand of redux-thunk as I am a newbie. I don't understand how I can send _REJECTED actions if I use the implementation of Typescript and redux-thunk documented here: https://redux.js.org/recipes/usage-with-typescript#usage-with-redux-thunk
There is a way to get the similar functionality without having three action types per thunk, but it will have some impact on the rendering logic.
I'd recommend pushing the transient aspect of the async calls down to the data. So rather than marking your actions as _PENDING, _FULFILLED, and _REJECTED, mark your data that way, and have a single action.
localUser.js (new file for the user type)
// Use a discriminated union here to keep inapplicable states isolated
type User =
{ status: 'ABSENT' } |
{ status: 'PENDING' } |
{ status: 'FULLFILLED', data: { fullName: string } } |
{ status: 'REJECTED', error: string };
// a couple of constructors for the fullfilled and rejected data
function dataFulFilled(data: { fullName: string }) {
return ({ status: 'FULLFILLED', data });
}
function dataRejected(error: string) {
return ({ status: 'REJECTED', error });
}
localUserReducer.js
const initialState: { user: User } = { user: { status: 'ABSENT' } };
export default function (state = initialState, action): { user: User } {
switch (action.type) {
case 'USER_CHANGED':
return {
...state,
user: action.payload
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'USER_CHANGED', payload: { status: 'PENDING' } });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'USER_CHANGED', payload: dataFulFilled(data) });
} catch (err) {
dispatch({ type: 'USER_CHANGED', payload: dataRejected(err.response.data) });
}
};
This will also remove the need for the multiple boolean fields (fetching and fetched) and isolate the various data states from accidental modification.
The changes to the render logic will be necessary, but will likely be an improvement. Rather than combinations of nested if-else statements using the booleans, a single switch can be used to handle the four cases of the data state.
Then you can invoke something like this from your render function...
function userElement(user: User) {
switch (user.status) {
case 'ABSENT':
return <></>;
case 'PENDING':
return <div>Fetching user information...Please be patient...</div>;
case 'FULLFILLED':
return <div>{user.data.fullName}</div>;
case 'REJECTED':
return <h1>The error is: {user.error}</h1>
}
}
I hope that helps. Good luck!

Using the useMemo() hook in React is causing my function to be a step behind

I am using the useMemo hook return and filter an array of items. I then have a toggle function that toggles whether an item is true or false and then posts that item back to an API if it is true or false and adds it to a list. Within the function, which using the useReducer hook, the array is one step behind. For instance, the array of items gets returned and you toggle whether they are on sale or not, and if you toggle true they get added to the saleList and if they get toggled to not on sale they get added to notSaleList. In the function the saleList length will come back as 3 but it is really 4, then you remove a home to make it 3 but it will return 4. Does anybody know why that would be thanks?
const homesReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false,
};
case 'FETCH_SUCCESS':
//action.payload to object
const entities = action.payload.reduce((prev, next) => {
return { ...prev, [next.Id]: next };
}, {});
return {
...state,
isLoading: false,
isError: false,
homes: entities,
};
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
};
case 'TOGGLE_SALE_HOME_INIT':
return {
...state,
homes: {
...state.homes,
// ask Jenkins
[action.payload]: {
...state.homes[action.payload],
IsSaleHome: !state.homes[action.payload].IsSaleHome,
},
},
};
case 'TOGGLE_SALE_HOME_SUCCESS':
return {
...state,
};
case 'TOGGLE_SALE_HOME_FAILURE':
// TODO update back if failed
return {
...state,
homes: {
...state.homes,
// ask Jenkins
[action.payload]: {
...state.homes[action.payload],
IsSaleHome: !state.homes[action.payload].IsSaleHome,
},
},
};
default:
return { ...state };
}
};
const useOnDisplayApi = activeLotNumber => {
const [state, dispatch] = useReducer(homesReducer, {
isLoading: false,
isError: false,
homes: [],
saleHomes: [],
});
const homes = useMemo(() => {
return Object.keys(state.homes).map(id => {
return state.homes[id];
});
}, [state.homes]);
}
const saleHomes = useMemo(() => {
return homes.filter(home => {
return home.IsSaleHome;
});
}, [homes]);
const notSaleHomes = useMemo(() => {
return homes.filter(home => {
return !home.IsSaleHome && !home.IsSuggestedSaleHome;
});
}, [homes]);
const toggleSaleHome = async (home, undo = true) => {
dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
try {
const didUpdate = await updateInventory(
activeLotNumber,
home.InventoryId,
{
InventoryId: home.InventoryId,
IsSaleHome: !home.IsSaleHome,
}
);
if (didUpdate == true) {
dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
}
else {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
}
} catch (error) {
setTimeout(() => {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
}, 600);
}
};
The update after dispatch isn't available immediately and is asynchronous. So your app will go through another render cycle to reflect the update.
You need to use useEffect to call the api after update and not call it on initial render.
const initialRender = useRef(true);
useEffect(() => {
if(initialRender.current) {
initialRender.current = false;
} else {
try {
const didUpdate = await updateInventory(
activeLotNumber,
home.InventoryId,
{
InventoryId: home.InventoryId,
IsSaleHome: !home.IsSaleHome,
}
);
if (didUpdate == true) {
dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
}
else {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
}
} catch (error) {
setTimeout(() => {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
}, 600);
}
}
}, [home])
const toggleSaleHome = async (home, undo = true) => {
dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
}
I ended up fixing my issue by adding an if statement before the 'INIT'.
const toggleSaleHome = async (home, undo = true) => {
if (saleHomes.length > 9 && !home.IsSaleHome) {
toast.error(
<div>
{`${home.Name} could not be added. You already have selected 10 sale homes.`}
</div>,
{
className: 'error-toast',
progressClassName: 'error-progress-bar',
closeButton: false,
position: toast.POSITION.BOTTOM_RIGHT,
}
);
return;
}
dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
try {
const didUpdate = await updateInventory(
activeLotNumber,
home.InventoryId,
{
InventoryId: home.InventoryId,
IsSaleHome: !home.IsSaleHome,
}
);
if (didUpdate == true) {
dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
}
else {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
}
} catch (error) {
setTimeout(() => {
dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
}, 600);
}
};
My whole issue was that I was not wanting any more homes to be able to be toggled once they reached 10 and before the 'INIT' the actual state of saleHomes is available, so the saleHomes.length is accurate.

how to async/await redux-thunk actions?

action.js
export function getLoginStatus() {
return async(dispatch) => {
let token = await getOAuthToken();
let success = await verifyToken(token);
if (success == true) {
dispatch(loginStatus(success));
} else {
console.log("Success: False");
console.log("Token mismatch");
}
return success;
}
}
component.js
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
});
}
However, when I write component.js code with async/await like below I get this error:
Possible Unhandled Promise Rejection (id: 0): undefined is not a function (evaluating 'this.props.dispatch(splashAction.getLoginStatus())')
component.js
async componentDidMount() {
let success = await this.props.dispatch(splashAction.getLoginStatus());
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
}
How do I await a getLoginStatus() and then execute the rest of the statements?
Everything works quite well when using .then(). I doubt something is missing in my async/await implementation. trying to figure that out.
The Promise approach
export default function createUser(params) {
const request = axios.post('http://www...', params);
return (dispatch) => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
request.then(success => onSuccess, error => onError);
};
}
The async/await approach
export default function createUser(params) {
return async dispatch => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
try {
const success = await axios.post('http://www...', params);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
}
Referenced from the Medium post explaining Redux with async/await: https://medium.com/#kkomaz/react-to-async-await-553c43f243e2
Remixing Aspen's answer.
import axios from 'axios'
import * as types from './types'
export function fetchUsers () {
return async dispatch => {
try {
const users = await axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => res.data)
dispatch({
type: types.FETCH_USERS,
payload: users,
})
} catch (err) {
dispatch({
type: types.UPDATE_ERRORS,
payload: [
{
code: 735,
message: err.message,
},
],
})
}
}
}
import * as types from '../actions/types'
const initialErrorsState = []
export default (state = initialErrorsState, { type, payload }) => {
switch (type) {
case types.UPDATE_ERRORS:
return payload.map(error => {
return {
code: error.code,
message: error.message,
}
})
default:
return state
}
}
This will allow you to specify an array of errors unique to an action.
Another remix for async await redux/thunk. I just find this a bit more maintainable and readable when coding a Thunk (a function that wraps an expression to delay its evaluation ~ redux-thunk )
actions.js
import axios from 'axios'
export const FETCHING_DATA = 'FETCHING_DATA'
export const SET_SOME_DATA = 'SET_SOME_DATA'
export const myAction = url => {
return dispatch => {
dispatch({
type: FETCHING_DATA,
fetching: true
})
getSomeAsyncData(dispatch, url)
}
}
async function getSomeAsyncData(dispatch, url) {
try {
const data = await axios.get(url).then(res => res.data)
dispatch({
type: SET_SOME_DATA,
data: data
})
} catch (err) {
dispatch({
type: SET_SOME_DATA,
data: null
})
}
dispatch({
type: FETCHING_DATA,
fetching: false
})
}
reducers.js
import { FETCHING_DATA, SET_SOME_DATA } from './actions'
export const fetching = (state = null, action) => {
switch (action.type) {
case FETCHING_DATA:
return action.fetching
default:
return state
}
}
export const data = (state = null, action) => {
switch (action.type) {
case SET_SOME_DATA:
return action.data
default:
return state
}
}
Possible Unhandled Promise Rejection
Seems like you're missing the .catch(error => {}); on your promise. Try this:
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
})
.catch(err => {
console.error(err.getMessage());
}) ;
}
use dispatch(this.props.splashAction.getLoginStatus()) instead this.props.dispatch(splashAction.getLoginStatus())

Resources