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);
}
Related
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;
}
};
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!
I am currently editing some reducers to be able to track the loading state of axios operations. Most of my async syntax is written in async/await fashion and would like to keep it that way to keep my code organized.
I am not sure how to dispatch two action creators one after the other: the first one to fire off the FETCHING_USER action type and keep track of the reduced isFetching state, while the other one to fire off the actual axios GET request. The code currently looks like this to get the API request:
export const fetchUser = () => async dispatch => {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
};
I am not sure how to dispatch the FETCHING_USER and then fire off the FETCH_USER action.
First you need to modify your reducer to have isFetching statement and requesting and receiving data cases:
const INITIAL_STATE = { isFetching: false, data: [] };
export default(state = INITIAL_STATE, action) => {
switch(action.type) {
case REQUEST_USER: {
return {...state, isFetching: true};
}
case RECEIVE_USER: {
return {...state, isFetching: false, data: action.payload};
}
default: return state;
}
}
Then modify your action to use try/catch statements:
export const fetchUser = () => async dispatch => {
dispatch({ type: REQUEST_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: RECEIVE_USER, payload: res.data });
}
catch(e){
//dispatch your error actions types, (notifications, etc...)
}
};
Then in component you can use condition like: isFetching ? //show loader : //show content (data[])
it's my first time working with React and I'm having a few problems when I try to get some data. My problem is inside getChargesByUser() method where I just written two console.logs, the first one it shows but the second one not, that means that I'm getting inside the method but not into the return (dispatch) => {...} The weird thing is that it's not the only method I have with the return (dispatch) => {...} like loadUserList() and it's the only I cannot have access and don't know why
const SHOW_PAYMENTS = 'SHOW_PAYMENTS';
const initialState = {
open_drawer: false,
open_search: false,
list_users: [],
list_selectusers: [],
countries: [],
states: [],
list_payments: [],
user: [],
useremail: [],
save: [],
eventtype: '',
user_redirect: '',
saveloading: false
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case SHOW_PAYMENTS:
return Object.assign({}, state, {list_payments: action.payload, eventtype: 'payments'});
default:
return state;
}
}
export function loadUserList(page, perpage) {
/* I HAVE ACCESS HERE */
return (dispatch) => {
/* I ALSO HAVE ACCESS HERE */
axios.get(`${config.host}/v1/user?page=${page}&perpage=${perpage}`, {
headers: {'x-asd-apikey': config.apikey}
}).then(response => {
dispatch({type: LOAD_USERLIST, payload: response.data.data});
}).catch(error => {
dispatch({type: LOAD_USERLIST_ERROR, payload: error.response.data});
});
};
}
export function getChargesByUser(userid) {
console.log('yes'); //<-- I have access here
return (dispatch) => {
console.log('nope'); // I have not accesss here
axios.get(`http://localhost:7770/v1/payments/${userid}/resume`, {
headers: {'x-asd-apikey': config.apikey}
}).then(response => {
console.log('response: ', response.data.data);
dispatch({type: SHOW_PAYMENTS, payload: response.data.data.records});
}).catch(error => {
console.log('error: ', error.response.data);
dispatch({type: SHOW_PAYMENTS, payload: { options: error.response.data}});
});
};
}
And this is where I call the method
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments,
}, usersActions)
)
static propTypes = {
eventtype: PropTypes.any,
list_payments: PropTypes.any,
getChargesByUser: PropTypes.func.isRequired,
params: PropTypes.object
}
componentDidMount() {
console.log('params: ', this.props.params.userid);
this.props.getChargesByUser(this.props.params.userid);
}
When inside promises you need to return a promise-like object to continue the chain.
So: you need to return axios.get(... if you want to go inside then/catch after (axios call returns promises).
Sorry, I was like all the day with the problem and didn't notice that the problem it was at the #connect() where my code was
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments,
}, usersActions)
)
and it's supposed to be like this:
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments
}),
usersActions)
When users navigate to /places/:slug in my React/Redux app, an ajax call is triggered to get the relavant place data from the database.
While this all works as expected I'm not sure how to show a 404 if no place was found. I have a 404 route setup for when users navigate to a non-route but how can I trigger a 404 using Redux?
In my container component I have:
this.props.dispatch(fetchPlace(this.props.match.params.slug))
and my action:
import axios from "axios";
export function fetchPlace(slug) {
return function(dispatch) {
dispatch({type: "FETCH_PLACE"});
axios.get("/server/places/" + slug)
.then((response) => {
dispatch({type: "FETCH_PLACE_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACE_REJECTED", payload: err})
})
}
}
and my reducer:
const reducer = (state={
place: {},
isFetching: false,
error: null
}, action) => {
switch(action.type) {
case "FETCH_PLACE" : {
return { ...state, isFetching: true }
}
case "FETCH_PLACE_REJECTED" : {
return { ...state, isFetching: false, error: action.payload }
}
case "FETCH_PLACE_FULFILLED" : {
return { ...state, isFetching: false, place: action.payload }
}
default:
return state
}
}
export default reducer
Ideas
I could use another state property in my reducer called notFound and initialize it to false. Then read the response data payload and detect whether a job has been returned. If not then set notFound to true. But how do I listen for notFound to be true and trigger a 404?
I would suggest returning a promise from fetchPlace action and catch it in container.
Action code which returns promise
export function fetchPlace(slug) {
return function(dispatch) {
dispatch({type: "FETCH_PLACE"});
return axios.get("/server/places/" + slug)
.then((response) => {
dispatch({type: "FETCH_PLACE_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACE_REJECTED", payload: err})
})
}
}
Container code (You must have access to react-router context or history object)
this.props.dispatch(fetchPlace(this.props.match.params.slug))
.then(() => {})
.catch(() => {
//Redirect to 404 page using history object from props
this.props.history.replace(`404`)
//Redirect to 404 page using context
this.context.router.history.replace(`404`)
})
Another approach would be checking notFound value in componentWillReceiveProps(nextProps) method.