Global state with redux somehow switches variables - reactjs

I am very confused as to why this is happening as this has never happened with me before using redux. I am building a react native application and currently when I try to console log store.getStore() get the following output.
Object {
"userState": Object {
"currentUser": Object {
"email": "test120#gmail.com",
"username": "test13",
},
"listings": Array [],
},
}
Now, when I dispatch my fetchUserListings() action, which should update the listings in the state the following happens.
Object {
"userState": Object {
"currentUser": Array [
Object {
"addressData": Object {
"description": "2300 Yonge Street, Toronto, ON, Canada",
"matched_substrings": Array [
Object {
"length": 3,
"offset": 0,
},
],
"place_id": "ChIJx4IytjwzK4gRwIPk2mqEJow",
"reference": "ChIJx4IytjwzK4gRwIPk2mqEJow",
"structured_formatting": Object {
"main_text": "2300 Yonge Street",
"main_text_matched_substrings": Array [
Object {
"length": 3,
"offset": 0,
},
],
"secondary_text": "Toronto, ON, Canada",
},
"terms": Array [
Object {
"offset": 0,
"value": "2300",
},
Object {
"offset": 5,
"value": "Yonge Street",
},
Object {
"offset": 19,
"value": "Toronto",
},
Object {
"offset": 28,
"value": "ON",
},
Object {
"offset": 32,
"value": "Canada",
},
],
"types": Array [
"street_address",
"geocode",
],
},
"addressDescription": "2300 Yonge Street, Toronto, ON, Canada",
"bath": "6",
"benefits": Array [
"Large Beds",
"Nearby Bustop",
"In-building gym",
],
"urls": Array [
"https://firebasestorage.googleapis.com/v0/b/studenthousingfinder-11f55.appspot.com/o/listing%2FoHr0OMukEFguYxJborrvMAJQmre2%2F0.bd7cwka5gj?alt=media&token=81b3e06a-65a9-44a7-a32d-d328014058e7",
"https://firebasestorage.googleapis.com/v0/b/studenthousingfinder-11f55.appspot.com/o/listing%2FoHr0OMukEFguYxJborrvMAJQmre2%2F0.k78etqzypk?alt=media&token=e2622547-00f4-447b-8bea-799758734f0d",
],
},
],
"listings": Array [],
},
}
Basically the API call is working and the state is updated, however somehow the data sent back is updating the currentUser in the state rather than the listings.
Here is my current reducer code:
import {USER_LISTINGS_STATE_CHANGE, USER_STATE_CHANGE} from '../constants';
const initialState = {
currentUser: null,
listings: [],
};
export const userReducer = (state = state || initialState, action) => {
switch (action.type) {
case USER_STATE_CHANGE:
return {
listings: state.listings,
currentUser: action.payload,
};
case USER_LISTINGS_STATE_CHANGE:
return {
currentUser: state.currentUser,
listings: action.payload,
};
default:
return state;
}
};
and here are the 2 functions I use to make the API request
export function fetchUser() {
return async (dispatch) => {
db.collection('users')
.doc(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
console.log('Yo');
dispatch({type: USER_STATE_CHANGE, payload: snapshot.data()});
} else {
console.log('Does not exist');
}
});
};
}
export function fetchUserListings() {
return async (dispatch) => {
db.collection('posts')
.doc(auth.currentUser.uid)
.collection('userListings')
.orderBy('title', 'desc')
.get()
.then((snapshot) => {
let listingArr = snapshot.docs.map((doc) => {
const data = doc.data();
const id = doc.id;
return {id, ...data};
});
dispatch({
type: USER_LISTINGS_STATE_CHANGE,
payload: listingArr,
});
});
};
}
Any help would be appreciated as I'm really lost as to why this is happening!

It seems like that your users collection in database might be having wrong entry.
Fact that brought me to this conclusion is that in your fetchUserListings() function, you're actually adding an extra field to json object i.e id.
Now, the console.log output doesn't contain this id field which could only mean that fetchUserListings() is not the one being called here.
You can try putting some try catch block and console.log('fetchUser', snapshot.data()) & console.log('fetchUserListings', snapshot.docs) in respective functions to see which one is being called.

Related

How can i change the value of an element in an object from extrareducer redux

This is the initial state:
const initialState ={
ordersWholesale:[
{
"id": 14,
"name": "XTPara 650mg Tablet",
"code": "XTP5656",
"date": "17/10/2022",
"accepted": null,
"wholesale": "shakthi",
"quantity": "5"
},
{
"id": 15,
"name": "Dolo 650 Tablet",
"code": "DOL1213",
"date": "17/10/2022",
"accepted": false,
"wholesale": "shakthi",
"quantity": "5"
},
],
}
This is the slice reducer
extraReducer: {
[asyncOrderAccept.fulfilled]: (state, { payload }) => {
}
}
How can I change only the value orderWholesale[0]['accepted']: true using the payload value which is 14?
If I'm understanding your question correctly that the action payload is the id of the ordersWholesale state element you want to toggle true, then you'll need to search the array to find the correct element by id and then update that element. Keep in mind that state is the ordersWholesale array and that Array.prototype.find potentially returns undefined if no match is found.
extraReducer: {
[asyncOrderAccept.fulfilled]: (state, { payload }) => {
const el = state.find(order => order.id === payload);
if (el) {
el.accepted: true,
}
},
}
This may also work for you if you can use Optional Chaining.
extraReducer: {
[asyncOrderAccept.fulfilled]: (state, { payload }) => {
state.find(order => order.id === payload)?.accepted = true;
},
}

Sort data in Axios response and set as useReducer payload

I'm calling data from an api into my react app using axios, like so:
const adapter = axios.create({
baseURL: "http://localhost:4000",
});
const getData = async () => {
const response = await adapter.get("/test-api");
return response.data;
};
This runs in a context, and I have a basic reducer function that I pass to the context:
const initialState = {
loading: true,
error: false,
data: [],
errorMessage: "",
};
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.FETCH_SUCCESS:
return {
...state,
loading: false,
data: action.payload,
};
case ACTIONS.FETCH_ERROR:
return {
...state,
error: true,
errorMessage: "Error loading data",
};
default:
return state;
}
};
The data I'm returning from my api is shaped like this:
{
"data": [
{
"id": 1,
"name": "Name 1",
"items": [
{
"id": "klqo1gnh",
"name": "Item 1",
"date": "2019-05-12"
}
]
},
{
"id": 2,
"name": "Name 2",
"items": [
{
"id": "klqo2fho",
"name": "Item 1",
"date": "2021-05-05"
},
{
"id": "klro8wip",
"name": "Item 2",
"date": "2012-05-05"
}
]
}
]
}
And I've written a simple function that finds the item whose nested array, items here, has the earliest date, using moment:
const sortDataByDate = (items) => {
return items.sort((first, second) => {
if (moment(first.items.date).isSame(second.items.date)) {
return -1;
} else if (moment(first.items.date).isBefore(second.items.date)) {
return -1;
} else {
return 1;
}
});
};
I then fetch everything in this function:
const fetchData = useCallback(async () => {
try {
await getData().then((response) => {
dispatch({
type: ACTIONS.FETCH_SUCCESS,
payload: response,
});
});
} catch (error) {
dispatch({ type: ACTIONS.FETCH_ERROR });
}
}, []);
I then run fetchData() inside a useEffect within my context:
useEffect(() => {
fetchData();
}, [fetchData]);
All this to say, here's the problem. My sortDataByDate function works sporadically; sometimes the data is ordered correctly, other times it's not. What I'd like to do is fetch my data, sort it with sortDataByDate, and then set the payload with that sorted data, so it's sorted globally rather than on a component level. Inside my App it seems to work consistently, so I think that I have missed something on a context level. Any suggestions?
You need to sort inner items first and get the earliest date:
const sortDataByDate = (items) => {
return items.sort((first, second) => {
if (moment(first.items[0].date).isSame(second.items[0].date)) {
return -1;
} else if (moment(first.items[0].date).isBefore(second.items[0].date)) {
return -1;
} else {
return 1;
}
});
};

React: setState with spead operator seems to modify state directly

I am creating a page in React to filter attributes that are defined in my state with a isChecked like so:
this.state = {
countries: [
{ "id": 1, "name": "Japan", "isChecked": false },
{ "id": 2, "name": "Netherlands", "isChecked": true },
{ "id": 3, "name": "Russia", "isChecked": true }
//...
],
another: [
{ "id": 1, "name": "Example1", "isChecked": true },
{ "id": 2, "name": "Example2", "isChecked": true },
{ "id": 3, "name": "Example3", "isChecked": false }
//...
],
//... many more
};
I am creating a function resetFilters() to set all the isChecked to false in my state:
resetFilters() {
// in reality this array contains many more 'items'.
for (const stateItemName of ['countries', 'another']) {
// here i try to create a copy of the 'item'
const stateItem = [...this.state[stateItemName]];
// here i set all the "isChecked" to false.
stateItem.map( (subItem) => {
subItem.isChecked = false;
});
this.setState({ stateItemName: stateItem });
}
this.handleApiCall();
}
My problem is: it seems I am directly modifying state, something that is wrong, according to the docs. Even though my function seems to work, when I remove the line this.setState({ stateItemName: stateItem }); it will also seem to work and when I console log stateItem and this.state[stateItemName] they are always the same, even though I am using the spread operator which should create a copy. My question: how is this possible / what am I doing wrong?
That is because the spread syntax does only shallow copying. If you want to carry out deep copying, you should also be spreading the inner objects within each array.
for (const stateItemName of ['countries', 'another']) {
const stateItem = [...this.state[stateItemName]];
const items = stateItem.map( (subItem) => ({
...subItem,
isChecked: false,
}));
this.setState({ [stateItemName]: items });
}
I think your code could be reduced more so, for example an approach could be:
function resetFilters() {
const targetItems = ['countries', 'another'];
const resetState = targetItems.reduce((acc, item) => ({
...acc,
[item]: this.state[item].map(itemArray => ({
...itemArray,
isChecked: false
}))
}), {})
this.setState(state => ({ ...state, ...resetState }), this.handleApiCall);
}
The benefit here is that the api call is done after state is updated. While updating current state correctly.
Let me know how it works out 👌🏻
-Neil

how to decrement count value that is in a nested array within a reducer

I need to increment, and decrement on each click(its for a like button). I figured out the logic to increment a like value, but how would i go about decrementing the value ?
The count is pretty much the length of the array, how many likes for that particular post/image.
So here is the logic that increments the like value, and this works, it will increment to infinity, which is not what i want. Im looking for is the reducer logic that will take care of the decrement value, just as the POST_LIKE_SUCCESS does
console.log( newState.images.find(image => image.id === action.data).likes)
data structure as followed
Data structure.
{
"id": 154,
"image_title": "iiisdd",
"img_url": "https://res*******",
"created_at": "2019-07-18T19:44:49.805Z",
"updated_at": "2019-07-18T19:44:49.805Z",
"user_id": 1,
"user": {
"id": 1,
"googleId": null,
"username": "E*******",
"password": "$***********JwCO",
"email": "e******",
"created_at": "2019-06-23T18:57:17.253Z",
"updated_at": "2019-06-23T18:57:17.253Z"
},
"comments": [
{
"id": 51,
"comment_body": "owls life",
"created_at": "2019-07-18T20:04:51.484Z",
"updated_at": "2019-07-18T20:04:51.484Z",
"user_id": 8,
"image_id": 154,
"user": {
"id": 8,
"googleId": null,
"username": "guest",
"password": "$************",
"email": "***********l.com",
"created_at": "2019-07-18T20:04:34.315Z",
"updated_at": "2019-07-18T20:04:34.315Z"
}
},
{
"id": 52,
"comment_body": "dadad",
"created_at": "2019-07-19T20:16:40.103Z",
"updated_at": "2019-07-19T20:16:40.103Z",
"user_id": 1,
"image_id": 154,
"user": {
"id": 1,
"googleId": null,
"username": "**********",
"password": "$*********O",
"email": "e*******m",
"created_at": "2019-06-23T18:57:17.253Z",
"updated_at": "2019-06-23T18:57:17.253Z"
}
},
{
"id": 53,
"comment_body": "test",
"created_at": "2019-07-21T22:12:44.729Z",
"updated_at": "2019-07-21T22:12:44.729Z",
"user_id": 1,
"image_id": 154,
"user": {
"id": 1,
"googleId": null,
"username": "E******d",
"password": "$********4WjO",
"email": "********",
"created_at": "2019-06-23T18:57:17.253Z",
"updated_at": "2019-06-23T18:57:17.253Z"
}
}
],
"likes": [
{
"id": 24,
"user_id": 2,
"image_id": 154,
"created_at": "2019-07-22T19:26:27.034Z",
"deleted_at": "2019-07-22T19:26:27.034Z",
"restored_at": "2019-07-22T19:26:27.034Z",
"updated_at": "2019-07-22T19:26:27.034Z"
},
{
"id": 77,
"user_id": 1,
"image_id": 154,
"created_at": "2019-07-23T02:55:31.051Z",
"deleted_at": "2019-07-23T02:55:31.051Z",
"restored_at": "2019-07-23T02:55:31.051Z",
"updated_at": "2019-07-23T02:55:31.051Z"
}
]
}
I want to delete the like, similar to redux boilerplate counter like this.
state - 1
reducer
import {
UPLOAD_IMAGE_SUCCESS,
POST_COMMENT_SUCCESS,
DELETE_IMAGE_FAILURE,
FETCH_IMAGES_SUCCESS,
DISLIKE_POST_SUCCESS,
POST_COMMENT,
POST_LIKE,
POST_LIKE_SUCCESS,
POST_LIKE_FAILURE,
DELETE_IMAGE_SUCCESS,
} from '../actions/types';
const initialState = {
images: [],
likedByuser: false,
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return {
...state,
images: action.images,
};
.....
case DELETE_IMAGE_SUCCESS:
// console.log(action)
return {
...state,
images: state.images.filter(img => img.id !== action.data),
};
case DELETE_IMAGE_FAILURE:
return {
...state,
error: action.error,
};
case POST_LIKE_SUCCESS:
console.log(action.data);
const newState = { ...state }; // here I am trying to shallow copy the existing state;
const existingLikesOfPost = newState.images.find(image => image.id === action.data).likes;
console.log(existingLikesOfPost)
newState.images.find(image => image.id === action.data).likes = [...existingLikesOfPost, action.newLikeObject]; // using this approach I got some code duplication so I suggested the first approach of using **push** method of array.
// console.log(newState)
return newState;
case DISLIKE_POST_SUCCESS:
console.log(action.data)
// working on logic that will decrement
return{
...state - 1
}
default:
return state;
}
};
console.log(action.data)
{
"type": "POST_LIKE",
"data": {
"id": 154,
}
}
express backend logic
router.post('/like/:id', (req, res) => {
const { id } = req.params;
if (id !== null || 'undefined') {
Image.forge({ id })
.fetch({ withRelated: ['user', 'comments', 'likes'] })
.then((likes) => {
const like = likes.toJSON();
// console.log(like.likes);
const existingUserlikes = like.likes.map(user => user.user_id);
// checking to see if a user already liked his own post
// if existing user does not have user id, user can like post,
// else if user already like this post wont be able to.
const newLike = new Likes({
image_id: id,
user_id: req.user.id
});
if (existingUserlikes.includes(req.user.id)) {
// !newLike do not create a new row of likes if like from this user already exists
if (!newLike) {
Likes.forge().where({ user_id: req.user.id, image_id: id }).destroy();
}
return Likes.forge().where({user_id: req.user.id, image_id: id }).fetch()
.then((like) => like.destroy()
.then( () => res.json({ error: true, data: { message: 'like deleted' } })));
}
newLike.save().then(like => res.status(200).json({ status: 'You liked this post', like: newLike }));
});
}
});
I feel like you are complicating things. Personally, I wouldn't structure my data like that. Deeply nested objects should be avoided. You can instead have several objects that are linked to each other. Also, I would have the post ids as object keys, so when I want to update a specific post, I just need to
return {
...state,
[action.id]: {
...state[action.id],
...action.newData,
}
}
In your case, I would just filter out the user id that disliked the post:
...
case DISLIKE_POST_SUCCESS:
const newState = { ...state };
const predicate = ({ id }) => id === action.data;
const existingLikes = newState.images.find(predicate).likes;
// You would have to pass userId as an action payload as I'm assuming you are deleting
// the likes based on the userId.
const newLikes = existingLikes.filter(({ user_id }) => user_id !== action.userId);
newState.images.find(predicate).likes = newLikes;
The easiest thing would be to use an immutability-helper. This enables you to update an object immutable without worrying about the whole object.
You could update it like this:
return update(state, {
likes: {$remove: [action.id]}
});
If you change your like object later on, you don't have to change this part of the code, because it will only update the mentioned data.

How can I update an array in state?

When my app loads I generate some initial data. My initial state is:
const INITIAL_STATE = { calendarData: [ ], index: 0 }
The data generated is an array of objects:
[
{
"date": "2017-09-24T16:13:24.419Z",
"id": 0,
"swiped": false,
"uri": "http://www.someurl.com/24.png",
},
{
"date": "2017-09-25T16:13:24.426Z",
"id": 1,
"swiped": false,
"uri": "http://www.someurl.com/25.png",
},
{
"date": "2017-09-26T16:13:24.426Z",
"id": 2,
"swiped": false,
"uri": "http://www.someurl.com/26.png",
}
]
I'm attempting to update calendarData like so:
return {...state, calendarData: [...state.calendarData, ...aboveArray]};
However, when I log mapStateToProps
const mapStateToProps = state => {
return { calendarData: state.calendarData };
};
Console shows the following:
Object {
"calendarData": Object {
"calendarData": Array [],
"index": 0,
},
I am trying to update the empty calendarData array in INITIAL_STATE with the new array of objects.
Before I made this change I was initializing state = [] and returning the aboveArray, which worked. Now I want my state to be an object that has a calendarData array and an index key,
state = {
calendarData: [],
index: 0,
}

Resources