Reducer not updating props in component correctly - reactjs

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

Related

problems in request in unit test the status is undefined

I am trying to do a unit test to an action in my react application but apparently everything works fine, I get a message that I am not understanding when making the request and the status is undefined, I don't have any specific variable with the status name so I assume it must be a problem when making the promise. How can I solve this problem?
error : undefined | TypeError: Cannot read property 'status' of
undefined
at request (C:\Users\amils\OneDrive\Documentos\Bootcamp - Training\Project\tracking-tool-webapp\src\api\utilities\fetch.js:45:26)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at Object.getAll (C:\Users\amils\OneDrive\Documentos\Bootcamp -
Training\Project\tracking-tool-webapp\src\api\utilities\provider.js:18:9)
console log result:
console.log
[
{
title: 'Candidates',
errorMessages: [],
candidates: [],
reports: null,
loading: false,
programsInProgress: [],
programVersions: [],
statusType: []
},
{ onLoadCandidates: [Function: onLoadCandidates] }
]
The code:
it('Should get all candidates', async () => {
const mockResponse = {
candidates: [
{
id: '4fffc534-1d83-14d5-b264-1e17f2abd322',
name: 'Homer Simpson',
status: 'InProgress',
},
{
id: '4fffc535-1d83-14d5-b264-1e17f2abd322',
name: 'Junior Santos',
status: 'InProgress',
},
],
};
global.fetch = jest.fn(() => {
Promise.resolve({
status: 200,
json: () => Promise.resolve(mockResponse),
});
});
const result = await customRenderHook();
const actions = result.current[1];
console.log(result);
await act(async () => {
actions.onLoadCandidates();
});
const state = result.current[0];
expect(state.candidates).toEqual(mockResponse);
});
code customRenderHook:
const customRenderHook = () => {
const wrapper = ({ children }) => <CandidatesDataProvider>{children}</CandidatesDataProvider>;
const { result } = renderHook(() => useCandidatesContext(), { wrapper });
return result;
};
I find the problem, currently, I cant execure my promise without a tokes 'Bearer', now the problem here is how can I create a mock of token:
function onLoadCandidates(dispatch) {
dispatch({ type: CandidatesActionTypes.loading, payload: true });
const token = localStorage.getItem('token');
apiCandidate
.getAll(token)
.then((response) => {
dispatch({ type: CandidatesActionTypes.loading, payload: response.data });
})
.catch((err) => {
dispatch({ type: CandidatesActionTypes.Error, payload: err.message });
LoggerService.error(err);
})
.finally(() => {
dispatch({ type: CandidatesActionTypes.loading, payload: false });
});
}
You could mock localStorage.getItem to return a token in the format required by apiCandidate.getAll(token):
localStorage.getItem = jest.fn().mockReturnValue('your-token');

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.

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);
}

Updating deeply nested state in Redux

I'm updating a "message board" project I did (Brad Traversy's MERN stack course) to include comment likes. For a simple example, think Facebook: you create a post which people can like, and people can respond with comments, which can also be liked.
I have a "post" piece of state, which contains post likes (post.likes array), comments (post.comments array), and comment likes (where things get tricky: an array nested inside the post.comments array).
How do I update the nested arrays in my reducer so my page re-renders properly? Right now, it records the action and shows the new likes/dislikes when the page reloads manually, but it won't reload the page itself.
I've tried updating the state, but the reality is, I'm not entirely sure how to loop through and update something deeply nested.
Here is my actual post state, courtesy of Redux DevTools.
post: {
posts: [],
post: {
_id: '5cebd8bcdc17fd5cd7e57a45',
text: 'This is my brand new post.',
name: 'Bobby Smith',
avatar: '//www.gravatar.com/avatar/414868368393e3ba8ae5ff93eeb98de6?s=200&r=pg&d=mm',
user: '5cd646c9a632b51373121995',
likes: [
{
_id: '5cebd8d1dc17fd5cd7e57a47',
user: '5cd36ce5fda120050ee64160'
}
],
comments: [
{
date: '2019-05-27T12:32:16.172Z',
likes: [ /*-------- This ---------*/
{
_id: '5cebd8e1dc17fd5cd7e57a48',
user: '5cd646c9a632b51373121995'
}
],
_id: '5cebd8d0dc17fd5cd7e57a46',
text: 'And this is my brand new response.',
name: 'John Doe',
avatar: '//www.gravatar.com/avatar/b2b146dba9e0023cb56637f0df4aa005?s=200&r=pg&d=mm',
user: '5cd36ce5fda120050ee64160'
}
],
date: '2019-05-27T12:31:56.598Z',
__v: 3
},
loading: false,
error: {}
}
}
Reducer:
const initialState = {
posts: [],
post: null,
loading: true,
error: {}
}
export default function(state = initialState, action) {
const { type, payload } = action
switch (type) {
case UPDATE_COMMENT_LIKES:
return {
...state,
post: { ...state.post, comments: ???? }
}
default:
return state
}
}
It's passing in the post ID and the user ID, and then filtering based on whether or not they already exist. I'll also add the action creators, just for clarity.
// Add like to comment
export const addCommentLike = id => async dispatch => {
try {
const res = await axios.put(`/api/posts/comment/like/${id}`)
dispatch({
type: UPDATE_COMMENT_LIKES,
payload: { id, likes: res.data }
})
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
})
}
}
// Remove like from comment
export const removeCommentLike = id => async dispatch => {
try {
const res = await axios.put(`/api/posts/comment/unlike/${id}`)
dispatch({
type: UPDATE_COMMENT_LIKES,
payload: { id, likes: res.data }
})
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
loading: false
})
}
}
Right now, it's updating everything in the database, but it's not updating the state immediately and triggering a re-render.
I'd appreciate any help I can get.
Thanks!
I know it's a bit too late but it might help for someone. I also did this course btw ;)
So in your case reducer would look like this:
case UPDATE_COMMENT_LIKES:
return {
...state,
post: {
...state.post,
comments: state.post.comments.map((comment) =>
comment._id === payload.id
? { ...comment, likes: payload.likes }
: comment
)
},
loading: false
};
It would be better if you'll split the remove/add logic in your reducer as well:
ADD_COMMENT_LIKES - will add the like to the nested likes array
REMOVE_COMMENT_LIKES - will filter the chosen like by its id
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case ADD_COMMENT_LIKES:
return {
...state,
post: {
...state.post,
comments: [
...state.post.comments,
likes: [
...state.post.comments.likes,
payload <--- inserting the new like
]
]
}
}
case REMOVE_COMMENT_LIKES:
return {
...state,
post: {
...state.post,
comments: [
...state.post.comments,
likes: [
...state.post.comments.likes.filter((item) => item.id !== payload.id)
]
]
}
default:
return state
}
}
Read how to combine reducers, and form your state as a single independent branches. It will help you structure your reducer in a more maintainable structure.

React: cannot retrieve data from a server (dispatch)

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)

Resources