React useReducer is being called twice on each dispatch - reactjs

I have this code which seems to be executing each dispatch of the useReducer twice for some reason I don't get to resolve. I have tried to solve it by moving userReducer out of the component, but the results were the same. At this point I don't know how to approach the problem.
Another issue caused by the repetition of the code is that I create empty arrays [] to work with them but in the second repetition, it creates [''] over the empty array, which shouldn't happen.
Edit: Searching info, I disabled strict mode as well, but the problem persists.
I suggest to see the whole code here:
https://codesandbox.io/s/gallant-villani-zfwbpg?file=/src/App.js
(To trigger the problem, insert one number in the first input at the top, then press the button next to it, A red button will appear to restore previous values. When pressed, two consoles.log are displayed, this happens in every dispatch type.)
here is the useReducer:
function reducer(state, action) {
const arrayPueblosYexclus = ["pueblos_T1","pueblos_T2","pueblos_T3","exclusiones"];
switch (action.type) {
case "RESTORE":
console.log("restored");
return valorPrevio;
break;
case "FETCH_SUCCESS":
const datos = action.payload;
datos?.forEach((element) => {
arrayPueblosYexclus?.map(arel=>{
return element[arel] !== null
? (element[arel] = element?.[arel]
?.toString()
.trim()
.split(","))
: (element[arel] = []);
})
console.log(element.pueblos_T1,element.pueblos_T2,element.pueblos_T3);
});
setLoading(false);
setValorPrevio(state);
return { ...state, data: datos, loading: false, error: null };
break;
case "FETCH_ERROR":
return {
...state,
data: [],
loading: false,
error: Number(action.payload).toFixed(2),
};
break;
case "UPDATE_DATA":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.id) {
return { ...item, [action.property]: action.payload };
}
return item;
}),
};
break;
case "DELETE_FROM_VILLAGES":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.municipio_id) {
return {
...item,
[action.grupo]: item[action.grupo].filter(
(villa) => villa !== action.barrio
),
};
}
return item;
}),
};
break;
case "RISE_PRICES":
setValorPrevio(state);
return {
...state,
data: state?.data.map((item) => {
const updatedItem = { ...item };
const propertiesToUpdate = [
"precioT1_poco",
"precioT1_normal",
"precioT1_grua",
"precioT2_poco",
"precioT2_normal",
"precioT2_grua",
"precioT3_poco",
"precioT3_normal",
"precioT3_grua",
];
propertiesToUpdate.forEach((property) => {
if (
item[property] &&
item[property] !== 0 &&
item[property] !== "0"
) {
if (action.operation_type === "porcentual") {
updatedItem[property] = (
Number(item[property]) +
(Number(item[property]) * action.vlue) / 100
)
.toFixed(2)
.replace(/\.00$/, "");
setPercentage("");
}
if (action.operation_type === "normal") {
updatedItem[property] = (
Number(item[property]) + Number(action.vlue)
)
.toFixed(2)
.replace(/\.00$/, "");
setNormal("");
}
}
});
return updatedItem;
}),
};
break;
case "ADD_VILLAGE":
let villagesArray = action.villages.split(",").map(v=>v.trim().charAt(0).toUpperCase() + v.trim().slice(1));
return {
...state,
data: state.data.map((item,i) => {
if (item.id === action.selected_municipio) {
let villagescoincidence = villagesArray.filter(v => item[action.T].includes(v));
villagesArray = villagesArray.filter(v => !item[action.T].includes(v));
if (villagescoincidence?.length > 0) {
let coincidentes = villagescoincidence.join(", ");
console.log(`This message is appearing twice`);
}
return {
...item,
[action.T]: [...(item[action.T].filter(e => e !== "") || []), ...villagesArray],
};
}
return item;
}),
};
break;
default:
return state;
}
}

Related

React Native useSelector not updating state in Container

I'm working on a React Native app, and I'm having some issues regarding conditional rendering based on the Redux state, which I fetch using useSelector.
I have tried with and without the useEffect below (so with returning the component directly instead of setting a state), but the variables returned from useSelector do not seem to change when the state is updated. Since the states are loading at first, I end up in the latest else if, and get stuck there. I have to then refresh the app to get the actual values and get to the desired screen
const ContainerA = ({ navigation }) => {
const {
loginSuccess,
loginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
const [toRender, setToRender] = useState(null);
useEffect(() => {
if (loginSuccess) {
if (loginSuccess.success === 1 && accountType === 3) {
console.log('[Container] case 1');
setToRender(<PageA navigation={navigation} />);
// return <PageA navigation={navigation} />;
// return;
} else if (
(loginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) || (loginSuccess.success === 0)
) {
console.log('[Container] case 2');
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (loginLoading || permissionsLoading) {
console.log('[Container] case 4');
setToRender(<LoadingPage />);
// return <LoadingPage />;
// return;
}
}, [
loginSucess,
loginLoading,
accountType,
navigation,
permissionSuccess,
permissionsLoading,
]);
return toRender;
};
export default ContainerA;
Redux reducer:
case 'loginInit':
return updateState(state, {
loginLoading: true,
loginSuccess: null,
loginFail: null,
});
case 'loginSuccess':
return updateState(state, {
loginLoading: false,
loginSuccess: action.success,
});
case 'loginFail':
return updateState(state, {
loginLoading: false,
loginFail: action.error,
});
case 'permissionsInit':
return updateState(state, {
permissionsLoading: true,
accountType: null,
permissionsSuccess: null,
permissionsFail: null,
});
case 'permissionsSuccess':
return updateState(state, {
permissionsLoading: false,
permissionsSuccess: action.success,
accountType: action.success.success
? action.success.success
: action.success.errors,
});
case 'permissionsFail':
return updateState(state, {
permissionsLoading: false,
permissionsFail: action.error,
});
updateState function:
export const updateState = (state, updatedProps) => ({
...state,
...updatedProps,
});
Seems like I was executing the functions that do the checks in a place where I shouldn't get any successful response. I fixed it by calling those functions in the Navigator where I was sure to have the token, since those calls required it (and which were not doing the actual API call without it). The code remaining in ContainerA is:
const {
firstLoginSuccess,
firstLoginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
if (firstLoginSuccess) {
if (firstLoginSuccess.success === 1 && accountType === 3) {
return <FirstTimeLoginPage navigation={navigation} />;
} else if (
(firstLoginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) ||
firstLoginSuccess.success === 0
) {
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (firstLoginLoading || permissionsLoading) {
console.log('[FirstTimeLoginContainer] case 4');
}
return <LoadingPage />;

How do I check if an item exists and modify it or return the original object

export const basketReducer = (state = { total:0, items:[]}, action) => {
switch (action.type) {
case "ADD_ITEM":
const item = [...state.items, action.payload]
const updateBasket = state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return item
})
It seems your code is close.
First check if state.items array already contains some element with a matching _id property.
If there is a match then shallow copy the state and shallow copy the items array and update the matching element.
If there is no match then shallow copy the state and append the new data to the items array.
Reducer case logic:
case "ADD_ITEM":
const hasItem = state.items.some(el => el._id === action.payload._id);
if (hasItem) {
// update item
return {
...state:
items: state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return el; // <-- return current mapped element if no change
}),
};
} else {
// add item
return {
...state,
items: state.items.concat(action.payload),
};
}

Update the Old and New Value in Redux

I have a problem updating the old value of the array in my redux react app. I have successfully updated the new selected object true. I want to other object to set to false since I have set the another object to true.
const initialState = {
annualPlans: [],
};
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};
It seems like you want to return current set to false for the others:
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return with current false
{ ...todo, current: false }
Id first create the new todos by looping though a map and then assign to the state
case planConstants.UPGRADE_PLAN_SUCCESS: {
const newTodos = state.annualPlans.map((todo) => {
if (todo.value === action.data.plan_id) {
return { ...todo, current: true }; // if todo.id matched then set the current to true and return;
}
if (todo.current) {
return { ...todo, current: false }; // else if current is already true, then false it and return
}
return todo; // else return original todo
});
return {
...state,
annualPlans: newTodos
};
}
.....
This will optimize the rendering and prevent of looping multiple times.
No need to re create all todo items, if you use pure components then that will mess up this optimisation. You can just add a map to deal with resetting the current value:
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans
.map(
(todo) =>
todo.current
? { ...todo, current: false } //reset current to false
: todo // no need to change this one
)
.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};

Array.prototype.filter() is not giving expected output when i am using in my reducer

import {
SEARCH_CHAT_FROM_RECENT_CHAT_CONTAT_LIST,
GET_RECENT_CHAT_CONTAT_LIST_REQUEST,
GET_RECENT_CHAT_CONTAT_LIST_SUCCESS,
GET_RECENT_CHAT_CONTAT_LIST_FAILURE
} from "../actions/action-types";
const INTIAL_STATE = {
response: null,
error: null,
loading: false,
searchResults: null,
};
searchChatFromRecentChatContactList = (state, text) => {
if(state.response && state.response.length > 0) {
const response = [...state.response];
const searchResults = response.filter(item => item.displayName.includes(text));
return searchResults;
}
return [];
}
export default (state = INTIAL_STATE, action) => {
switch (action.type) {
case GET_RECENT_CHAT_CONTAT_LIST_REQUEST:
return { ...state, loading: true, response: null, error: null, };
case GET_RECENT_CHAT_CONTAT_LIST_SUCCESS:
return { ...state, response: action.payload, loading: false};
case GET_RECENT_CHAT_CONTAT_LIST_FAILURE:
return { ...state, response: null, error: action.payload, loading: false };
case SEARCH_CHAT_FROM_RECENT_CHAT_CONTAT_LIST:
return {...state, searchResults: searchChatFromRecentChatContactList(state, action.payload)};
default:
return state;
}
};
I have array of strings in my state.response but for some reason my below method is always returning [];
state.response = [{displayName: 'someText'}, {displayName: 'someText otherText'];
input:
searchChatFromRecentChatContactList(state, 'SomeText')
output:
[];
You can still improve it, destruction is not needed here because of nature of Array.prototype.filter, it returns newly created array
searchChatFromRecentChatContactList = (state, text) => {
const searchText = text.toLowerCase();
return state.response && state.response.length ?
state.response.filter(item => item.displayName.includes(searchText)) : [];
}
I did silly mistake :(
searchChatFromRecentChatContactList = (state, text) => {
if(state.response && state.response.length > 0) {
const searchText = text.toLowerCase();
const response = [...state.response];
const searchResults = response.filter(item => {
if(item.displayName.includes(searchText)) {
return true;
} else {
return false;
}
});
return searchResults;
}
return [];
}
text.toLowerCase(); //I should have done this. :-)

Delete an item from an array in Redux

I'm learning redux and I was wondering how to delete one item from the state. I have this initial state:
export const getInitialState = () => {
let state = {
isLogged: false,
organizations: [],
userData: {},
activeIndex: -1,
currentRetrospective: {},
hasFetched: false
}
This is how the data lives inside organizations
case `${actions.ACTION_GET_USER_ORGS}_FULFILLED`: {
let activeIndex = 0
if (state.activeIndex !== -1) {
activeIndex = state.activeIndex
} else if (action.payload.data.length === 0) {
activeIndex = -1
}
return { ...state, activeIndex, organizations: action.payload.data, hasFetched: true }
}
Now, what I need to do is to delete one item from the retrospectives array in an organization. I tried this but it doesn't work. Is there a better way to do it?
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
const { organizations, activeIndex } = state
const newOrganizations = JSON.parse(JSON.stringify(organizations))
const activeOrganization = newOrganizations[activeIndex]
activeOrganization.retrospectives = activeOrganization.retrospectives
.filter((retro) => retro.id != action.retroId )
return { ...state, organizations: newOrganizations }
}
Thank you!
you can filter the organization array like this:
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
return {
...state,
organizations: state.organization.filter(retro =>
retro.id !== action.retroId }
}

Resources