React/Redux: componentDidUpdate not firing consistently - reactjs

I have nested arrays in my props, and the user can re-order the second-level array by dragging and dropping column. I (using redux) make a shallow copy of getState's outer array, do the re-ordering (through re-assignment of array objects based on index), and return the new array to the state (using spread operator in my reducer). However, componentDidUpdate is not called if this ordering is the first thing I do after page load. If I take other actions that update the props/state, and then attempt to re-order, the ordering works fine and componentDidUpdate is called.
In the former situation, where ordering is the first thing I try to do after page load/refresh, the render function is hit (with the properly re-ordered props) but the DOM never updates, presumably because redux does not recognize a state change?
It just seems like some other props change (even if unrelated to arrays) "wakes up" redux or connect and allows it to recognize changes.
Here's where I copy, dispatch and then release the updated state in the reducer:
in actionCreator method:
...
const newProjects = getState().tracker.projects.slice().map(p => {
if (p.innerArray) {
const from = p.innerArray[draggedId-1];
const to = p.innerArray[droppedId-1];
p.innerArray[draggedId - 1] = { ...to, order: draggedId };
p.innerArray[droppedId - 1] = { ...from, order: droppedId };
}
return p;
})
dispatch({ type: 'RECEIVE_FIELD_UPDATE', projects: newProjects, message: "Sorted" });
reducer:
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: action.projects,
message: action.message
}
As you can see, I'm not mutating state in the reducer or doing other anti-pattern things (as far as I'm aware).
FWIW, the "Sorted" value on message property seems to get through whether or not componentDidUpdate is hit (I have a modal that pops up with the message to confirm).
Any pointers or related documentation would be helpful; I've been reading all I can find, including other StackOverflow posts, but to no avail.
UPDATE
Solved the issue, it did have to do with redux not diff-ing the properties but not because of what I expected, read below if interested.

Figured this out, and per usual I was looking in the wrong place.
The issue was that my conditional check on page load to prevent re-loading of already stored data was preventing any dispatches from firing. When I looked in the reducer, the sub-arrays of the projects were identical after dragging, because the state had not been previously connected yet. I thought a hard refresh would cause a full re-load but apparently I need to read more about how redux stores data and how to refresh that.
Anyways, fixing this condition so that I get new data on page load fixed the issue and componentDidUpdate is now being triggered consistently.

When updating state in a reducer in an array format, I've had to spread state again in order for updates to happen and keep from mutating.
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: ...state, ...action.projects,
message: action.message
}
or...
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: [...state, ...action.projects],
message: action.message
}
Depending on the format.
Hope that helps.

Related

How to use an array as a dependency of a React hook

I have a component that has a callback. It depends on an array of plain old objects stored in redux which won't change very often while the component itself will change its state pretty frequently. Some subcomponents should be rerendered on those state changes, but the one that uses the callback, should not.
What's the best approach to making an array a dependency of useCallback()? So far, I've been using
const handleAllItemsSelectedChange = useCallback(
checked => {
if (checked) {
dispatch(setSelected(items));
} else {
dispatch(selectSelected([]));
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(items)]
);
This doesn't seem ideal, and potentially slower than just rerendering the component every time. I can't imagine this isn't a very common use-case. The React team surely has a best practice for this, right? I can't find it in the documentation.
JSON.stringify or any deep comparison is going to be inefficient and slow. React has no plans to support it
Depending on whether you add or remove items (if not mutating the objects) you can just compare with items.length. Or you could possibly save performance by just creating the function each time, as opposed to trying to save performance putting it in a useCallback.
It's a case by case scenario
In redux reducer every time the array changes you have to create new array. So, your array becomes immutable and you can use it for dependency by reference. Example below just demonstrates the principle.
function reducer(state, action) {
switch(action.type) {
case "addItem":
return {...state, items: [...state.items, action.value]};
case "changeProp":
return {...state, prop: action.value}
default:
return state;
}
}
As you can see every time the array changes you'll get the new instance of the array. That means you can use it by reference and don't need to strignify it anymore:
const handleAction = useCallback(checked=>{
....
}, [items]);
By the way immutability is the approach recommended by redux documentation

Cloning an object within a redux reducer

I've recently learned React and Redux, and right when I thought I was getting the hang of it I've been hung up on this.
In my reducer I'm under the impression that the spread operator should create a new instance of the object at a difference memory location. However, using === to compare the objects returns true which I believe means the objects are the same instances.
const initState = {
loadcases: {
1: {
isActive: false,
profile: 'UB'
},
2: {
isActive: true,
profile: 'PFC'
}
}
}
const designInput = (state=initState, action={}) => {
let newState = {...state}; //clone design input state
console.log(newState.loadcases === state.loadcases)
// Prints: TRUE ---- WHYYYYYYY????????????
// goes on to modify newState before returning it.
}
The redux logger is showing my previous state to already equal the new state which has me thinking that this here is interfering with the store directly. I've read through some redux docs and other posts which make me believe I have set everything else up correctly.
Any resources to educate me more on this issue is also much appreciated.
UPDATE:
Jayce's answer was exactly what was happening.
This SO post outlines which method for a deep clone will be suitable. For me, it's likely I'll want to store dates and other data so I've chosen to use lodash cloneDeep method.
So now: console.log(cloneDeep(state) === state.loadcases); // false
Your assumption that the spread operator creates a new object is true, however it doesn't do a deep clone of objects. It copies the references of nested values over to the new object, so that's why it's printing that they're equal.
If you want to deeply clone something with nested values you can't use spread, you have to use another method. You could use a library like lodash, or something such as JSON.parse(JSON.stringify(obj)), there's multiple options just google for something like "Javascript deep clone object"

Tons of components are updating without any relevant change in state

I'm trying to diagnose some intermittently awful performance in my React app, and can see lots of components updating without any change in props or state.
I've got a socket connection where whenever another user is typing, it broadcasts the fact that they're typing - this is then stored in my reducer under typingUserData.
Now, pretty much every component re-renders whenever this is updated, even though they never reference this variable, nor is it included in mapStateToProps, nor do any of their children.
I can use shouldComponentUpdate to prevent updates in one component, but then every one of its children re-renders anyway. Is it intended that I should use pretty much the same shouldComponentUpdate on every component?
My understanding was that if I can prevent it higher up in the tree, lower components won't unnecessarily render, but they're doing that regardless. I guess I'm asking:
a) is there a way of finding out WHY they're choosing to re-render, and
b) is there a way of saying "don't render and none of your children should render either" in shouldComponentUpdate?
EDIT:
To add a little more context, I'm receiving typing data from my socket connection like so:
window.Echo.join('account_'+accountId).listenForWhisper('typing', e => {
dispatch({
type: TYPING_SET,
payload: e,
})
}
This is merged into my state like so:
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case TYPING_SET:
let typingUserData = cloneDeep(state.typingUserData)
typingUserData[action.payload.userId] = {
isTyping: action.payload.isTyping,
chatId: action.payload.chatId
}
return {
...state,
typingUserData
}
}
}
SOME components listen to this from mapStateToProps like so:
function mapStateToProps(state) {
return {
typingUserData: state.account.typingUserData,
}
}
...but 99% of the components don't. For whole trees that are re-rendered, nothing in the tree references typingUserData at all.
I'm using react-redux 5.0.7.
As far as I can detect, nothing is mounting twice.
Selecting 'highlight updates' in Chrome's React console tools shows pretty much everything lighting up as a result of this. Removing the action for 'typing' makes it all go away.
Hope that's enough information! Sorry for the big ol' wall of text.

Two simultaneous api calls, redux state properties becomes empty

I call two action creators from a view component, which triggers two api calls from redux-sagas.
Both reducers have the same Action type - I don't know if that's good or bad, but it means that both reducers are triggered even if the data is handled by the other reducer.
Here is the first reducer:
case ADD_THINGS:
return {
...state,
termNodes: {
...action.payload.termNodes,
},
terms: {
...action.payload.terms,
},
};
And the next one:
case ADD_THINGS:
return {
...state,
...action.payload.shippingProfiles,
};
default:
return state;
The result is that on the first ADD_THINGS, both terms and shippingProfiles data gets put into state correctly, but on the second ADD_THINGS, both shippingProfiles and term data becomes empty.
In redux devtools I see:
ADD_THINGS <--- data is available on both shippingProfiles and terms
ADD_THINGS <--- shippingProfiles and terms data is gone
I don't understand how on the first ADD_THINGS both data gets synced and put into state correclty , and I also don't understand how both data gets erased? Any ideas?

Redux DevTools Time Travel fails to update UI correctly (only when reverting actions)

I am in the early development stage of a React+Redux game and have followed Redux best practices: pure reducer, presentational/container component separation, using getState() only in Reducer (as opposed to in action creator) etc. The app seems to be working as expected but when I try to reverse an action using Time Travel, even though the state property map[][] and it's computed connected component prop change as expected, the result doesn't get reflected on the UI properly (specifically the player position on the map doesn't follow what state dictates). When I inspect the state changes I can see that all necessary changes are correctly taking place between different states. Here is my reducer:
const gridReducer = (state, action) => {
if (typeof state === 'undefined'){
let dungeon = new Dungeon();
dungeon.generate();
return {
boardWidth: Math.floor(((70/100) * window.innerWidth) / 20),
boardHeight: Math.floor(((70/100) * window.innerHeight) / 20),
map: dungeon.map,
position: dungeon.playerPosition
}
}
switch (action.type) {
case 'GRID_RESIZE':
return {...state,
boardWidth: action.newBoardWidth,
boardHeight: action.newBoardHeight
}
//This is where I have the issue, map correctly changes both when interacting with the game and when reversing using time travel however the UI fails to update (only in reverse)!
case 'MOVE':
let dungeonObj = new Dungeon(state.map.slice(), {...state.position});
if (dungeonObj.movePlayer(action.direction)) {
return {...state,
position: dungeonObj.playerPosition,
map: dungeonObj.map
}
} else return state;
default:
return state;
}
}
Here is the complete code if you want to take a look! The app currently only supports moving the player in the dungeon by pressing arrow keys and the view is supposed to always be centeral based on the position of the player (player fails to move back when using time travel)
http://s.codepen.io/sabahang/debug/GjrPNQ
PS: Dungeon.generate does use Math.Random but I'm only using this function in initialState and for dispatched actions I'm only making a shallow copy of the generated map by sending the current state to Dungeon constructor and use its other methods (eg. movePlayer)
Found the culprit. It's not Redux's fault at all, it's about the way React works! If you are new to React and you haven't fallen into this trap yet wait for it!
It has to do with the fact that most of the conventional ways of copying a deeply nested object which is needed in Redux to implement a pure Reducer is in fact making a shallow copy of the objects and properties' memory references are still pointing to the original State. React updates the UI based on a deep comparison of the old state and the new one and when some of the references are the same it fails to update the UI properly. Here I have a 2 dimensional array map[][] which is an object and although I'm using ES6 spread operator to avoid modifying the original state because a shadow copy is being made, deeply nested indexes of the original map[][] are being modified. One solution would be to use `Array.map()' to create a completely new object but I ended up using immutablejs and it fixed my problem with the time travel slider.
This is a highly recommended reference if you don't want to spend weeks chasing similar bugs in complicated apps: http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
and there are tons of immutability helpers to help based on your specific need:
https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities
This one also looks interesting for Redux only:
https://github.com/indexiatech/redux-immutablejs
This question is potentially a duplicate of the following:
React-redux store updates but React does not

Resources