React: cannot retrieve data from a server (dispatch) - reactjs

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)

Related

Error: Invariant failed: A state mutation was detected between dispatches

Reducer :
function listPeopleByName (state = {
getPeopleName:{}
}, action){
switch(action.type){
case C.LIST_PEOPLE_NAME:{
return {
...state
,getPeopleName :action.payload
}
}
default : {}
}
return state
}
Action:
function listPeopleByName(config) {
return function (dispatch) {
ApiService(config)
.then((resp) => {
dispatch({
type: C.LIST_PEOPLE_NAME,
payload: resp.data,
});
})
.catch((error) => {
dispatch({
type: C.LIST_PEOPLE_NAME,
payload: error,
});
});
};
}
ApiService is a function that make an axios request and returns a respones
Dispatching code :
listPeopleByNameFunction = () => {
const listPeopleByNameParam = {
id: someone,
},
let data = {
PeopleId: "snjenfner",
};
let listPeopleByNameCategory = getApiConfig(
"POST",
listPeopleByNameParam,
data
);
this.props.listPeopleByName(listPeopleByNameCategory);
};
const mapDispatchToProps = (dispatch) => ({
listPeopleByName: (config) => dispatch(listPeopleByName(config)),
});
Although I take the previous state (...state) and change the state with the payload i'm getting, it still shows the state is mutated. I would have used reduxtoolkit but this is a way old project that doesn't need to be migrated to reduxtoolkit.

Concat arrays in redux reducer

I'm using an Unsplash API, and trying to make pagination. I don't want to replace one page with another, but to call more images results and concate it to an existing ones. This is my action:
export const getUnsplashImages = (page = 1) => async (dispatch) => {
dispatch({ type: GET_UNSPLASH_LOADING });
try {
const { data } = await unsplashUrl.get(
`${Constants.UNSPLASH_PHOTOS}?page=${page}&${Constants.UNSPLASH_CLIENT_ID}&per_page=13`
);
dispatch({ type: GET_UNSPLASH, payload: data });
} catch (error) {
dispatch({ type: GET_UNSPLASH_ERROR });
}
};
And I'm trying to do the concatenation in reducer:
const initState = {
images: [],
loading: false,
error: '',
};
export default function (state = initState, action) {
switch (action.type) {
...
case GET_UNSPLASH:
console.log('STATE', state);
return {
...state,
loading: false,
images: [...state.images, action.payload],
};
...
default:
return state;
}
}
But I'm getting an array inside an array. I tried to do it like this images: [...state.images, ...action.payload], But result is not as I expected. I'm getting a result of 26 images, instead of concatenating the results
Assuming payload: data is an array that you want to concat within images, you need to spread it:
images: [...state.images, ...action.payload]

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!

Reducer not updating props in component correctly

My comments are dissappearing from my component after didMount() initializes them? It's really strange!
React component:
componentDidMount = (post) => {
const postId = this.props.post.id
console.log('postpreview', postId)
this.props.fetchComments(postId)
console.log('postpreview comments:', this.props.comments)
}
Redux Actions:
export const beginFetchComments = () => ({
type: C.BEGIN_FETCH_COMMENTS,
})
export const fetchCommentsFailed = (error) => ({
type: C.FETCH_COMMENTS_FAILED,
payload: { error },
})
export const fetchCommentsSuccess = (comments) => ({
type: C.FETCH_COMMENTS_SUCCESS,
payload: { comments }
})
export function fetchComments(postId) {
return dispatch => {
dispatch(beginFetchComments());
return fetch(`${api}/posts/${postId}/comments`, { headers })
.then(
res => res.json(),
error => console.log('An error occurred at fetchComments', error)
)
.then(json => {
dispatch(fetchCommentsSuccess(json));
return json;
});
};
}
Redux Reducer (switch case):
case C.BEGIN_FETCH_COMMENTS:
return {
...state,
loading: true,
error: null
};
case C.FETCH_COMMENTS_SUCCESS:
console.log(action.payload.comments);
const comments = _.mapKeys(action.payload.comments)
return {
...state,
loading: false,
comments,
};
The console displays this for the same console.log(), (I can't get my hands on my props!):
(2) [{…}, {…}]0: {id: "894tuq4ut84ut8v4t8wun89g", parentId: "8xf0y6ziyjabvozdd253nd", timestamp: 1468166872634, body: "Hi there! I am a COMMENT.", author: "thingtwo", …}1: {id: "8tu4bsun805n8un48ve89", parentId: "8xf0y6ziyjabvozdd253nd", timestamp: 1469479767190, body: "Comments. Are. Cool.", author: "thingone", …}length: 2__proto__: Array(0)
commentsReducer.js:22 []
I don't know what is the use of mapKeys here but what I would do is do a console.log to see if I'm getting an object and under what key there is a comments array:
case C.FETCH_COMMENTS_SUCCESS:
console.log(action.payload.comments); // is this logging an array?
return {
...state,
loading: false,
comments: action.payload.comments,
};
The bottom code I posted is the console.log - the object appears populated and then rerenders empty

Resources