Component not updating on deeply nested redux object - reactjs

I have a project portion of my app, and users can create events within the project. My redux state is deeply nested, which looks like:
When users create an event, I update state with the following:
case CREATE_PROJECT_TODO:
const data = [...state.data];
const index = state.data.findIndex(project => project._id===action.payload.project_id);
data[index].events = [
...data[index].events,
action.payload
];
return {
...state,
data
};
However, my react component isn't updating to reflect the changes. I'm quite sure I'm not mutating state. Is it an issue with deeply nested objects, and react can't detect those changes! Any help would be appreciated!

With const data = [...state.data], you are doing a shallow copy.
Use map and update your state. Your state is updated correctly and will trigger the component re-render properly.
case CREATE_PROJECT_TODO:
const index = state.data.findIndex((project) => project._id === action.payload.project_id)
const updatedData = state.data.map((item, idx) => {
if (idx === index) {
return {
...item,
events: [...item.events, action.payload],
}
}
return item
})

Related

Redux reducer doesn't cause re-render after mapping to new array or using immer

In my redux reducer I map over state and copy internal items, then return the list, the list should be a new reference so the reducer should cause a re-render on change, but it doesn't.
The code below does not cause a re-render.
const initialState: Group[] = [];
export default function activeGroups(state = initialState, action: AnyAction) {
switch (action.type) {
case 'groups/createPod/fulfilled': {
// append the new pod into the active group that matches the groupId
const { pod, groupId } = action.payload;
const newState = state.map((group) => { // Map to new list
if (group.id === groupId) {
return {
...group, // Not mutating original state, copying the state into new list
pods: [pod, ...group.pods],
};
}
return group;
});
return newState; // This does not cause a re-render, why?
}
I've tried produce from immer
case 'groups/createPod/fulfilled': {
// append the new pod into the active group that matches the groupId
const nextState = produce(state, (draft) => {
const group = draft.find((e) => e.id === action.payload.groupId);
if (group) {
group.pods.unshift(action.payload.pod);
}
});
return JSON.parse(JSON.stringify(nextState)); // Even tried this
}
Figured it out. Redux is re-rendering as normal. The problem was unrelated to Redux.
It was because the object I was trying to update was being passed as a navigation param in react native, and this does not update with state changes (the reference is to the object passed through navigation, not to the Redux state).
Using useSelector() solves it.

React : reducer isn't updating my state when using Object.assign

I use a reducer to update this state :
const [playlist, dispatch] = useReducer(jspfReducer,new playlistModel());
It uses my playlistModel class, which is just a wrapper that adds some methods to manipulate easily my data - I need it.
I want to update the state, but avoid as much possible unecessary renders.
So when calling the reducer case UPDATE_JSPF_TRACK; I update only the matching track of the playlistModel track array.
function jspfReducer(state, action){
switch (action.type) {
case 'UPDATE_JSPF_TRACK':
const [index,jspf] = action.payload;
//update only that single track.
const newTracks = state.track.map(
(track, i) => i === index ? new trackModel(jspf) : track
);
const newState = Object.assign(
state,
{
track:newTracks
}
);
//my object value is correctly updated here :
console.log("NEW STATE",newState);
return newState;
break;
}
};
The value logged in the console is correctly updated.
But in my provider, the state update is not detected:
export function PlaylistProvider({children}){
const [playlist, dispatch] = useReducer(jspfReducer,new playlistModel());
//when state updates
useEffect(()=>{
console.log("PLAYLIST HAS BEEN UPDATED!!!",playlist);//does not fire
},[playlist])
What is wrong and how could I fix this ?
Thanks !
You'll need to create a new object for the state changes to be detected:
const newState = Object.assign(
{},
state,
{
track: newTracks,
},
);
or
const newState = { ...state, track: newTracks };
The way React works is it detects if the state objects have different referential equality, which basically means that if you only modify an object's attributes, it will still be considered the same object for the purposes of rendering. To trigger state change effect you'll need to create a new object.

Programmatically observing changes in Recoil with React

I'm using Recoil (recoiljs.org) in my react project.
I have a fairly classic setup with a list of items - the object graph looks something like this:
Routes - routes is an array of Route objects
- Route - route object is edited in a form
I use an atom to represent the currently selected Routes array, e.g.
const [routes, setRoutes] = useRecoilValue(currentRoutesViewState);
And I also use a selectorFamily to allow individual controls to bind to and update the state of an individual item.
const routeSelectorFamily = selectorFamily({
key: "routeSelectorFamily",
get:
(routeKey) =>
({ get }) => {
const routes = get(currentRoutesViewState);
return routes.find((r) => r.key === routeKey);
},
set:
(routeKey) =>
({ set }, newValue) => {
set(currentRoutesViewState, (oldRoutes) => {
const index = oldRoutes.findIndex((r) => r.key === routeKey);
const newRoutes = replaceItemAtIndex(
oldRoutes,
index,
newValue as RouteViewModel
);
return newRoutes;
});
},
});
As you can see, the set function allows someone to update an individual route state and that is replicated up into anything rendering routes.
However, I would like to auto-save any changes to this whole graph but don't know how to easily observe any changes to the Routes parent object. Is there a way to programmatically subscribe to updates so I can serialize and save this state?
You can just use a useEffect for that:
const currentState = useRecoilValue(currentRoutesViewState);
useEffect(() => {
// currentState changed.
// save the current state
}, [currentState])

Redux state is not updating into the react component

How the redux state is updated into the react component?
I tried using initial state and not mutating the object and return that
Redux
const initialState = {
filteredProviderData:[],
filteredAlsoSpeaksData:[],
filteredOfficeHours:[]
};
function reducer(state = initialState, action = {}) {
switch (action.type) {
case HANDLE_FILTER_CHANGE:
let filteredProviderData = '';
let filteredAlsoSpeaksData='';
let filteredOfficeHours = ''
return {...state, filteredProviderData,filteredAlsoSpeaksData,filteredOfficeHours};
case RESET_FILTER_COLLECTION:
// RESET the Array as shown into the HANDLE_FILTER_CHANGE
}}
React component
const mapStateToProps = state => {
return {
filteredProviderData:state.providerList && state.providerList.filteredProviderData,
filteredAlsoSpeaksData:state.providerList && state.providerList.filteredAlsoSpeaksData,
filteredOfficeHours:state.providerList && state.providerList.filteredOfficeHours
}}
Here my question is how to update the array which is into the HANDLE_FILTER_CHANGE
RESET_FILTER_COLLECTION
I need to update array based on some condition and return that updated array
Here into the based on the condition only one array will be updated and return that only two would remains same.
You need to change the local state once you updated the redux store. this.setState({loading:true}) Kind of approach will work and it works for me too.

Redux: Reducer not mutating state in first attempt

I am working on a Table in React based application using typescript. I am implementing search functionality for the table. There is a huge amount of data that can be displayed inside table so I am performing search, sorting, pagination all at back end.
I have a component for the table which receives data as props from a parent component. I am using react with redux and using sagas, I get the data from back end. I am using redux state to provide data to component. I am using reducers to mutate the state.
The problem I am facing is that when I reload the data, I get the data and using reducer I mutate the state but that is not being displayed at frontend. But when I try second time, it displays the data.
My code for reducer is below.
const dataEnteries: Reducer<any> = (
state = {},
{ type, detail, pagination }: DataResponse
) => {
switch (type) {
case actionTypes.DATA_LOAD_RESPONSE:
if (!detail) {
return {};
}
const data: any[] = [];
const tableData: any[] = [];
detail.forEach((o) => {
tableData.push(o.dataDetail);
Data.push(o)
})
const resultMap = new Map()
resultMap["data"] = data;
resultMap["tableData"] = tableData;
resultMap["pagination"] = pagination;
return resultMap;
default:
return state;
}
};
here is my map state to props function
const mapStateToProps = ({ data }: { data: DataState }): DataProps => ({
data: data.dataEnteries
});
dataEnteries is the innermost property
I am unable to figure out what is going wrong in my case as at second time, things works rightly.
You could try to use spread operator on return.
return ...resultMap
When you update a prop inside an object the state didn't recognise the change then the render will not be called to refresh the component.
Try this, if not work let me know.
For further referente check this : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Resources