State didn't change in Redux - reactjs

I have this reducer that has in initial state and array of plans that has array of visits that has an array of treatments.
The idea is when action (Remove_treatment) is triggered, it removes one of the treatment as following:
import { REMOVE_TREATMENT } from "./treatmentPlanTypes";
const initialState = {
plans: [
{
id: 1,
visits: [
{
id: 1,
treatments: [
{
id: 1,
name: "treatment1",
},
{
id: 2,
name: "treatment2",
},
],
},
{
id: 2,
treatments: [
{
id: 1,
name: "treatment1",
},
{
id: 2,
name: "treatment2",
},
],
},
],
},
],
};
const treatmentPlanReducer = (state = initialState, action) => {
switch (action.type) {
case REMOVE_TREATMENT:
let clonePlans = [...state.plans];
const visitIndex = clonePlans[0].visits.findIndex(
(i) => i.visitId === action.visitId
);
const treatmentIndex = clonePlans[0].visits[
visitIndex
].treatments.findIndex((i) => i.treatmentId === action.treatmentId);
clonePlans[0].visits[visitIndex].treatments.splice(treatmentIndex, 1);
console.log(JSON.stringify(clonePlans));
return {
...state,
plans: [...clonePlans],
};
default:
return state;
}
};
When I console.log(clonePlans) I get the correct array.
But when I return the value like:
return {
...state,
plans: [...clonePlans],
};
I'm still having the old plan and not updated in the component.
If I tried to enter the expected plan as I get from console.log directly in the above return statement, the component is updated successfully.
this is a part of the component in my code:
const plans = useSelector((state) => state.treatmentPlan.plans, shallowEqual)
const listOfPlans = plans.map((plan) => (
<PlanArea key={plan.planId} visits={plan.visits} />
));
console.log(listOfPlans);
The component is still showing the old plan, but console.log(listOfPlans) shows the correct array.

You need to deeply copy your plans array for your component to feel the change in state, and hence re-render
let clonePlans = JSON.parse(JSON.stringify(state.plans))
return { ...state, plans: clonePlans };

Related

Redux/Toolkits, useSelector Doesn't Work, Why?

I want to save my data in localstorage to evade the loss of it when reloading the page but i also need it in my gloable state to show a preview of it once it's added and never be lost when reloading the page,This is my slice format:
import { createSlice } from "#reduxjs/toolkit";
export const resumeSlicer = createSlice({
name: "resume",
initialState: {
Education: [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
],
},
reducers: {
SaveEducation: (state, action) => {
let Education = JSON.parse(localStorage.getItem("Education"));
if (!Education) {
Education.push(action.payload);
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
} else {
Education.push(action.payload);
let i = 0;
Education.map((e) => {
e.key = i;
i++;
return e.key;
});
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
}
},
getEducation: (state, action) => {
const items = JSON.parse(localStorage.getItem("Education"));
const empty_array = [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
];
state.Education.splice(0, state.Education.length);
state.Education = items;
},
},
});
And this is how i fetched:
const EdList = useSelector((state) => state.Education);
When i console.log it the result is "undefined"
Image Preview
https://i.stack.imgur.com/hD8bx.png
I'm hazarding a guess that the issue is a missing reference into the state. The chunk of state will typically nest under the name you give the slice, "resume" in this case. This occurs when you combine the slice reducers when creating the state object for the store.
Try:
const EdList = useSelector((state) => state.resume.Education);
If it turns out this isn't the case then we'll need to see how you create/configure the store and how you combine your reducers.

How to properly update the reducer

In my initial state I have boards, think of them as groups in a chat room.
setboard is a variable use to switch between rooms using
activeBoard. =>
activeBoard: state.boards[initialState.setBoard],
I map the content of debates easily. The problem comes when I try to update the reducer.
const initialState = {
setBoard: 'Feed',
boards : {
Feed: {
id: 1,
debates: [
{
id: 1,
text: 'This is the most amazing website on the internet.",
Images: 'soul.jpg',
},
{
id: 2,
topic: 'Somebody tell him to shut up',
text: "This is the most amazing website on the internet.",
Images: 'salt.jpg',
},
],
invitations: [
{
id: 1,
nickname: "imhotep",
inviteText: ' BLOCKING People block or unfriend their parents on Facebook
},
],
}
export const BoardProvider = ({ children}) =>{
const [state, dispatch] = useReducer(BoardReducer, initialState)
function AddDebates(debates){
dispatch({
type: 'Add-debates',
payload: debates
})
}
return ( <BoardContext.Provider value={{
boards: state.boards,
activeBoard: state.boards[initialState.setBoard],
debates: activeBoard.debates,
AddDebates
}}>
{children}
</BoardContext.Provider>)
}
This is my reducer.
export default ( state, action, activeBoard, debates,invitations) =>{
switch(action.type) {
case 'Add-debates':
return {
...state,
debates: [action.payload, ...debates]
}
default:
return state
}
}
I get an error: TypeError: debates is not iterable
I can render debates by simply mapping it but can update reducer this way. Some help pls...
You need to shallowly copy all levels of state from root to debates that you are updating as it is nested a few levels deep. The correct reference will also include the full path to that property, i.e. state.debates is undefined and not iterable.
case 'Add-debates':
return {
...state,
boards: {
...state.boards,
Feed: {
...state.boards.Feed,
debates: [action.payload, ...state.boards.Feed.debates],
},
},
}

What happens when a reducer returns 'state' in React?

If I have a contactReducer that looks like this:
import {
GET_CONTACTS,
DELETE_CONTACT,
ADD_CONTACT,
EDIT_CONTACT,
GET_CONTACT,
} from "../actions/types";
// the state the holds the contacts
const initialState = {
contacts: [
{
id: 1,
name: "John Doe",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "Karen Williams",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
contact: {},
testProp: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
console.log("get contacts");
return {
...state,
};
case DELETE_CONTACT:
return {
...state,
contacts: state.contacts.filter(
(contact) => contact.id !== action.payload
),
};
case ADD_CONTACT:
let newArray = state.contacts.slice(); // get the current contacts array
newArray.unshift(action.payload); //push on the new contact to the beg of array
return {
...state, //take the existing state..
contacts: newArray,
};
case EDIT_CONTACT:
console.log("trying to edit");
return {
...state,
contacts: state.contacts.map((contact) =>
contact.id == action.id ? (contact = action.payload) : contact
),
};
case GET_CONTACT:
const selectedContact = getSingleContactFromId(state, action.payload);
console.log("look here");
console.log(selectedContact);
return {
...state,
contact: selectedContact,
testProp: { test: "test prop" },
};
default:
console.log("testing action in default");
return state;
}
}
function getSingleContactFromId(state, id) {
var contact;
console.log("get single contact");
for (var i = 0; i < state.contacts.length; i++) {
contact = state.contacts[i];
if (contact.id == id) {
return contact;
}
}
}
What is actually happening when the reducer returns? Where does it return to? For example, I send a dispatch to the reducer like this this.props.addContact(newContact);
But, I don't see that I do anything with the returned object anywhere after this. In another file, is where I grab things from the state, so does return really just mean it is updating the state?
Assuming you use combineReducers, the returned state from a specific reducer will now be the updated state of the state chunk represented by this reducer.
Then, any connected component will receive the new state and will re-render.
This is a high-level description obviously.
More information can be found here: https://react-redux.js.org/using-react-redux/connect-mapstate

Filter products depend on another ACTION in React-native Redux

I have an app which get all categories and products from the server with Redux ACTIONS. I need to filter products with a category Id. after load data action is complete, i call another action to filter products but i'm a little bit confused.
There is codes of few parts of the app:
ProductsActions:
export const GET_INITIAL_PRODUCTS_DATA = "GET_INITIAL_PRODUCTS_DATA";
export const GET_INITIAL_PRODUCTS_DATA_RESULT = "GET_INITIAL_PRODUCTS_DATA_RESULT";
export const GET_INITIAL_PRODUCTS_DATA_ERROR = "GET_INITIAL_PRODUCTS_DATA_ERROR";
export const FILTER_PRODUCTS_BY_CATEGORY_ID = "FILTER_PRODUCTS_BY_CATEGORY_ID";
export const getInitialProductsData = () => ({
type: GET_INITIAL_PRODUCTS_DATA
});
export const filterProductsByCategoryId = categoryId => ({
type: FILTER_PRODUCTS_BY_CATEGORY_ID,
categoryId
});
ProductsReducers:
import {
GET_INITIAL_PRODUCTS_DATA,
GET_INITIAL_PRODUCTS_DATA_RESULT,
GET_INITIAL_PRODUCTS_DATA_ERROR,
FILTER_PRODUCTS_BY_CATEGORY_ID
} from "../actions/products";
const initialState = {
isFetching: false,
data: {},
error: null
};
const filterProductsByCategoryId = (state, action) => {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_INITIAL_PRODUCTS_DATA:
return {
...state,
isFetching: true
};
case GET_INITIAL_PRODUCTS_DATA_RESULT:
return {
...state,
isFetching: false,
data: action.result
};
case GET_INITIAL_PRODUCTS_DATA_ERROR:
return {
...state,
isFetching: false,
error: action.error
};
case FILTER_PRODUCTS_BY_CATEGORY_ID:
return {
...state,
data: filterProductsByCategoryId(state, action.categoryId)
};
default:
return state;
}
};
export default reducer;
And there is my code to call filter action:
filterProducts = (title = "A") => {
const _categories = Object.values(this.props.categories);
const selectedCategory = _categories.find(
category => category.title === title
);
this.props.dispatch(filterProductsByCategoryId(selectedCategory.id));
My questions is:
A) Is there is a way to filter my data and display them in UI and refresh them without using ACTIONS way??
B) If A's answer is No!, How can i get my state.data and filter them in FILTER_PRODUCTS_BY_CATEGORY_ID?
Thanks.
You can use the Array.prototype.filter() to return filtered result.
keep in mind that this will return an array and not a single value, which is a good thing if you are using this filter within your reducer. because your reducer's shape is an array and not an object.
Running example:
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, action) => {
return state.filter(c => c.id === action.categoryId);
};
const result = filterProductsByCategoryId(myData, {categoryId: 2});
console.log(result);
I think it is more appropriate to create a selector for a singular product that will handle this kind of action, this way you will be able to return an object instead of an array with one product in it.
Not to mention the benefits of using reselect to do some memoizations.
For this task you can use the Array.prototype.find():
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, id) => {
return state.find(c => c.id === id);
};
const result = filterProductsByCategoryId(myData, 2);
console.log(result);

Overwriting array of objects instead of updating it

Im currently learning redux and trying few options out. Everything looks okay until I want to update one state in the array of objects.
I'm dispatching 5 actions in total now, first 2 setting longitutde and latitude for one part of the state using one reducer, then I set IDs with other reducer and finally when I want to update one of the objects in the array I update one but delete the other somehow.
My file looks like this:
const demoState = {
renderedDrugs: [
{
id: '',
drugId: '',
price: undefined
}
],
apt: {
latA: '',
lonA: ''
}
}
//SET_ID
const setId = (id, drugId) => ({
type: 'SET_ID',
renderedDrugs: {
id,
drugId
}
})
//SET_PRICE
const setPrice = (drugId, price) => ({
type: 'SET_PRICE',
drugId,
price
})
//RENDERED DRUGS REDUCER
const renderedDrugsReducerDefState = [];
const renderedDrugsReducer = (state = renderedDrugsReducerDefState, action) => {
switch (action.type) {
case 'SET_ID':
return [
...state,
action.renderedDrugs
]
case 'SET_PRICE':
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
})
default:
return state;
}
}
//SET_LAT
const setLat = (latA) => ({
type: 'SET_LAT',
latA
})
//SET_LON
const setLon = (lonA) => ({
type: 'SET_LON',
lonA
})
//APT REDUER
const aptReducerDefState = []
const aptReducer = (state = aptReducerDefState, action) => {
switch (action.type) {
case 'SET_LAT':
return {
...state,
latA: action.latA
}
case 'SET_LON':
return {
...state,
lonA: action.lonA
}
default:
return state;
}
}
//STORE CREATION
const store = createStore(
combineReducers({
renderedDrugs: renderedDrugsReducer,
apt: aptReducer
})
)
store.subscribe(() => {
console.log('store', store.getState());
})
store.dispatch(setLat(88));
store.dispatch(setLon(78));
store.dispatch(setId(uuid(), 3));
store.dispatch(setId(uuid(), 35));
store.dispatch(setPrice(35, {price: 400}));
I assume the SET_PRICE action is at fault, but I tried various configurations and cant figure out the issue so thats why I posted the whole file, if thats unnecessary let me know and I will delete irrelevant bits.
Console log after 4th dispatch:
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
{id: "2a3c4bca-610a-4554-b7e3-695ae6e30ae7", drugId: 3}
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object
and aftr 5th
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
undefined
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35, price: 400}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object
The .map doesn't return the unchanged objects for the items your're not updating. Adding a return should fix it:
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
return drug; // this was missing before, so the return was undefined
})

Resources