Redux initial state causes undefined error - redux

I am following an udemy course to learn redux coding on codePen, however, I got this following error.
redux.min.js:1 Uncaught TypeError: Cannot read property 'name' of undefined
at policies (pen.js:61)
at redux.min.js:1
at Array.forEach (<anonymous>)
at redux.min.js:1
at e.combineReducers (redux.min.js:1)
at pen.js:73
it tells me that it is because the name from the policies reducer function could not read the action.payload.name because it's undefined. I did not see the instructor have the same problem, but I have faithfully checked my code and it's identical to his. I just can't understand how I can fix it to display the state correctly.
I have debugged and tried to fix the problem, but so far the code keeps throwing error unless I delete the action.payload.name from the policies reducer function.
console.clear();
// people dropping off a form (Aaction Creator)
const createPolicy =(name, amount)=>{
// console.log(name,amount,'test createpolicy')
return { // Action (a form in our analogy)
type:'CREATE_POLICY',
payload: {
name:name,
amount: amount
}
};
};
const deletePolicy = (name) =>{
return {
type:'DELETE_POLICY',
payload:{
name: name
}
}
}
const createClaim = (name, amountOfMoneyToCollect) =>{
return {
type:'CREATE_CLAIM',
payload:{
name: name,
amountOfMoneyToCollect: amountOfMoneyToCollect
}
};
};
// Reducers (Departments!)
// default oldListOfClaims=[] is for initial state.
const claimsHistory=(oldListOfClaims=[], action)=>{
if(action.type==="CREATE_CLAIM"){
// we care about this action (FORM!)
return [...oldListOfClaims, action.payload];
}
// we don't care the action (FORM!)
return oldListOfClaims;
};
const accounting=(bagOfMoney=100, action)=>{
if(action.type==='CREATE_CLAIM'){
return bagOfMoney - action.amountOfMoneyToCollect;
}
else if(action.type==='CREATE_POLICY'){
return bagOfMoney + action.payload.amount;
}
return bagOfMoney;
}
const policies = ( listOfPolicies = [], action)=>{
if(action.type = 'CREATE_POLICY'){
console.log( action.payload,'test playload name')
return [...listOfPolicies,action.payload.name]
}
else if(action.type='DELETE_POLICY'){
return listOfPolicies.filter(name =>
name!==action.payload.name);
}
return listOfPolicies;
};
const { createStore, combineReducers} = Redux;
const ourDepartments = combineReducers ({
accounting: accounting,
claimsHistory: claimsHistory,
policies: policies
})
const store = createStore(ourDepartments);
console.log(store)
store.dispatch(createPolicy('Alex', 20));
store.dispatch(createPolicy('Jim', 30));
store.dispatch(createPolicy('Bob', 40));
console.log(store.getState());
I would expect to see the console.log of the current state after the new names and amounts are dispatched to the store. and figure out why this error keeps occurring.

Review your policies reducer - you are assigning rather than comparing against action types when checking against your types. Update it to the code below:
const policies = ( listOfPolicies = [], action)=>{
// previously if(action.type = 'CREATE_POLICY'){
if(action.type === 'CREATE_POLICY'){
console.log( action.payload,'test playload name')
return [...listOfPolicies,action.payload.name]
}
// previously else if(action.type='DELETE_POLICY'){
else if(action.type==='DELETE_POLICY'){
return listOfPolicies.filter(name =>
name!==action.payload.name);
}
return listOfPolicies;
};

Related

Why my Redux App return that [ Immer ] error?

I don't know. Why even I added my push function on my object to return my new result, The app is printing error on my console.log.
slice.js
import { createSlice } from '#reduxjs/toolkit';
import { pushProduct } from '../commons/push';
export const slice = createSlice({
name: 'initial',
initialState : {
product: [],
},
reducers: {
ADDS(state, actions) {
return {
...state,
product: pushProduct(state.product, actions.payload),
console1: console.log('State: ', state.product),
console2: console.log('Actions: ', actions.payload),
}
}
}
});
export const { ADDS } = slice.actions;
export default slice.reducer;
push.js
// Push new prpduct to the cart
export const pushProduct = (initial, productSelect) => { return initial.push(productSelect) };
console.log error
errors.ts:49 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
Thank You
Per the error message: Immer lets you update the state in two ways. One is "mutating" the existing state, and the other is returning a new value. But, you can only do one of those at a time.
You're trying to do both. You have return {...state}, but you also have pushProduct() which sounds like it's mutating.
The best answer here is to not try to do return {...state} at all, and just "mutate" the existing state.
See https://redux-toolkit.js.org/usage/immer-reducers#mutating-and-returning-state for more details.

Updating displayed results after modifying Firestore doc React Native

I have a list of games that I'm able to add to without issue using UseEffect and onSnapshot. I can modify an item in the list without issue, and return one set of results (with the updated data properly displaying). When I try to modify another item (or the item same again), I get this error:
Could not update game: TypeError: undefined is not an object (evaluating '_doc.data().numPlayers') because the results/list of games are null. I'm sure I have something wrong with my code, but I can't figure it out.
Thanks in advance!
Here is my code:
useEffect(() => {
setIsLoading(true)
let results = [];
const unsubscribe = db
.collection('games')
.onSnapshot(
(querySnapshot) => {
querySnapshot.docChanges().forEach(change => {
const id = change.doc.id;
if (change.type === 'added') {
const gameData = change.doc.data();
gameData.id = id;
results.push(gameData);
}
if (change.type === 'modified') {
console.log('Modified game: ', id);
results = results.map(game => {
if (game.id === id) {
return change.doc.data()
}
return game
})
console.log(results)
}
if (change.type === 'removed') {
console.log('Removed game: ', id);
}
});
setIsLoading(false);
setGame(results);
return () => unsubscribe
},
(err) => {
setIsLoading(false);
console.log("Data could not be fetched", err);
}
);
}, []);
I forgot to add the doc ID to the gameData before adding it to the results. I did that in the "added" section, but not in the "modified" section (thinking that it was already included), forgetting that I hadn't added it as an actual field in the database (it just exists as the doc id).

How to get all items from subcollection Firebase Firestore Vue

How do I get all the comments from the subcollection?
This is mine reusable function to get comments collection.
import { ref, watchEffect } from 'vue';
import { projectFirestore } from '../firebase/config';
const getCollection = (collection, id, subcollection) => {
const comments = ref(null);
const error = ref(null);
// register the firestore collection reference
let collectionRef = projectFirestore
.collection(collection)
.doc(id)
.collection(subcollection);
const unsub = collectionRef.onSnapshot(
snap => {
let results = [];
snap.docs.forEach(doc => {
doc.data().createdAt && results.push(doc.data());
});
// update values
comments.value = results;
error.value = null;
},
err => {
console.log(err.message);
comments.value = null;
error.value = 'could not fetch the data';
}
);
watchEffect(onInvalidate => {
onInvalidate(() => unsub());
});
return { error, comments };
};
export default getCollection;
And this is mine Comments.vue where i passing arguments in setup() function (composition API)
const { comments } = getAllComments('posts', props.id, 'comments');
When i console.log(comments) its null, in snapshot doc.data() is good but somehow results too is empty array even if i push doc.data() to results array and pass it to comments.value.
Can someone help me how to get that subcollection?
This is my Comment.vue component
export default {
props: ['id'],
setup(props) {
const { user } = getUser();
const content = ref('');
const { comments } = getAllComments('posts', props.id, 'comments');
const ownership = computed(() => {
return (
comments.value && user.value && user.value.uid == comments.value.userId
);
});
console.log(comments.value);
}
return { user, content, handleComment, comments, ownership };
},
};
const getCollection = (collection, id, subcollection) => {
const comments = ref(null);
const error = ref(null);
// Firestore listener
return { error, comments };
}
The initial value of comments here is null and since Firebase operations are asynchronous, it can take a while before the data loads and hence it'll log null. If you are using comments in v-for then that might throw an error.
It'll be best if you set initial value to an empty array so it'll not throw any error while the data loads:
const comments = ref([]);
Additionally, if you are fetching once, use .get() instead of onSnapshot()

Handling errors with redux-toolkit

The information about the error in my case sits deeply in the response, and I'm trying to move my project to redux-toolkit. This is how it used to be:
catch(e) {
let warning
switch (e.response.data.error.message) {
...
}
}
The problem is that redux-toolkit doesn't put that data in the rejected action creator and I have no access to the error message, it puts his message instead of the initial one:
While the original response looks like this:
So how can I retrieve that data?
Per the docs, RTK's createAsyncThunk has default handling for errors - it dispatches a serialized version of the Error instance as action.error.
If you need to customize what goes into the rejected action, it's up to you to catch the initial error yourself, and use rejectWithValue() to decide what goes into the action:
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
if (!err.response) {
throw err
}
return rejectWithValue(err.response.data)
}
}
)
We use thunkAPI, the second argument in the payloadCreator; containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options: For our example async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) is our payloadCreator with the required arguments;
This is an example using fetch api
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const getExampleThunk = createAsyncThunk(
'auth/getExampleThunk',
async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) => {
try{
const response = await fetch('https://reqrefs.in/api/users/yu');
if (!response.ok) {
return rejectWithValue(response.status)
}
const data = await response.json();
return fulfillWithValue(data)
}catch(error){
throw rejectWithValue(error.message)
}
}
)
Simple example in slice:
const exampleSlice = createSlice({
name: 'example',
initialState: {
httpErr: false,
},
reducers: {
//set your reducers
},
extraReducers: {
[getExampleThunk.pending]: (state, action) => {
//some action here
},
[getExampleThunk.fulfilled]: (state, action) => {
state.httpErr = action.payload;
},
[getExampleThunk.rejected]: (state, action) => {
state.httpErr = action.payload;
}
}
})
Handling Error
Take note:
rejectWithValue - utility (additional option from thunkAPI) that you can return/throw in your action creator to return a rejected response with a defined payload and meta. It will pass whatever value you give it and return it in the payload of the rejected action.
For those that use apisauce (wrapper that uses axios with standardized errors + request/response transforms)
Since apisauce always resolves Promises, you can check !response.ok and handle it with rejectWithValue. (Notice the ! since we want to check if the request is not ok)
export const login = createAsyncThunk(
"auth/login",
async (credentials, { rejectWithValue }) => {
const response = await authAPI.signin(credentials);
if (!response.ok) {
return rejectWithValue(response.data.message);
}
return response.data;
}
);

Firebase Firestore returns a promise in Vue

I'm trying to use some data from from Firestore. before it used to work, now in Vuetify I keep getting 'PENDING' if I try to access the $data.users
export default {
data() {
return {
users: [],
};
},
created() {
db.collection('users').get().then((snapshot) => {
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
this.users = user;
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
});
},
methods: {
imageUrl(user) {
console.log(user.documents.selfie.url); // Here the log return "Pending";
},
Inside the template I run a v-for (user, index) in users :key='index'
ERROR:
Uncaught (in promise) TypeError: Cannot read property 'selfie' of undefined
It's difficult to be 100% sure without reproducing your problem, but I think the problem comes from the fact that the Promise returned by the asynchronous get() method is not yet fulfilled when you call the imageUrl() method. This is why you get the pending value.
One possibility to solve that is to check as follows:
methods: {
imageUrl(user) {
if (user) {
console.log(user.documents.selfie.url);
} else {
//...
}
},
Also, is seems you want to populate the users Array with the docs from the users collection. You should do as follows:
created() {
db.collection('users').get().then((snapshot) => {
let usersArray = [];
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
usersArray.push(user);
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
this.users = usersArray;
});
},
With your current code you assign the last user in the loop, not the list of users.

Resources