My initial version did not use cloning. However useSelector() was not firing until I added another clone statement.
I thought that by cloning the containing object on a state change this would cause it tor fire. Particularly this line here.
let newState = { ...state };
That is a sub question. Why does not returning a new state cause useSelector to fire.
I had to add a second clone to get it to fire as follows:
const clone = [...newState.messages];
I find this behavior odd, and wonder if it might be a bug. Also not very efficient to have to clone my messages array.
The complete reducer is as follows:
const Messages = (state = {messages: false}, action) => {
let newState = { ...state };
switch(action.type) {
case 'initializeMessages':
newState.messages = action.messages;
return newState;
case 'addMessage':
// cloning is required to make useSelector() fire
const clone = [...newState.messages];
clone.unshift(action.message);
newState.messages = clone;
return newState;
}
return state;
};
and I use it as follows:
Updating
const messages = useSelector( (state) => state.Messages.messages );
Dispatching
dispatch({type: 'addMessage', message: message});
UPDATE
After updating to hooks, my reduces don't work anymore unless I clone embedded arrays. Before I did not have to do this:
you are right, you need to do the same for addMessage block like:
const newState = {
...state,
messages: [
...newState.messages,
action.message
]
};
Related
Lets imagine a simple reducer:
const [state, setState] = React.useReducer(myReducer, {})
And myReducer with one single case (simplified version):
const myReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_STATE': {
// Code to update here...
}
}
}
Now, which of the following statements is best suited to update a state - and why:
NOTE: payload = state.
METHOD 1
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object = true
return updatedState
}
METHOD 2
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object= true
return { ...state, updatedState }
}
METHOD 3
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object= true
return { ...state, ...updatedState }
}
METHOD 4
case 'UPDATE_STATE': {
return ({ ...state, object: true })
}
I think you should Use method 4. You are not mutating the state here, which is the React way.
case 'UPDATE_STATE': {
return ({ ...state, object: true })
}
Note: Spread operator ... creates different references up to one level only.
EDIT: Based on comments, here is a simple example of how you could be mutating state, even with the simplest of objects if you are not using ....
let state = { object : true};
let payload = state;
state.object = false;
console.log(state);
console.log(payload);
Even with ... you might have deeply nested objects. They will have the same problem, unless you want to spread at every level. There are libraries to help you with deep objets like immutable.js.
I'm using the Redux Toolkit and I'm struggling to find a way to update state within my store that also triggers a reassignment for useSelector.
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state.push(action.payload); // This updates the store but doesn't respect immutability?
}
}
});
I'm aware the above isn't entirely correct, and that something like
state = [...state, ...action.payload]
would be better, BUT for some reason I couldn't get it work correctly any other way. I'm simply trying to add an object to the array.
My component:
export default function App() {
const array = useSelector(selectArray);
return (
{array.map((x) => {
<div>{x.text}</div>
})
)
}
The issue is, whenever the dispatch is called, array doesn't update, which I'd like it to.
I think your issue is the way you push the new value into the array. That is not immutable and it appears the selector is detecting that the array hasn't changed, so it returns the previous value.
Try this:
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state = [ ...state, action.payload ];
}
}
});
This demo should simulate what happens when mutably changing state vs immutably changing state.
const state = {
list: [1]
}
const addItemMutable = (item) => {
const prevState = { ...state }
state.list.push(item)
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
const addItemImmutable = (item) => {
const prevState = { ...state }
state.list = [ ...state.list, item ]
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
addItemMutable(2)
addItemImmutable(3)
Cannot work out what is going on here but basically i have a json file which has a load of products. im trying to then render the ones I want
here is my reducer:
export default(state = initialState, action) => {
switch(action.type){
case Types.SHOW_PRODUCTS: {
console.log('here1');
let productsToShow = data.filter(category => category.link === action.category)
const newState = [].concat(productsToShow[0].products)
return newState;
}
default:
console.log('here2');
return state;
}
}
when I log the state in my store, it says that productsToRender is an array of length 5 (this is correct)
however, when I log (this.props) in one of my components, it says that the length is 0
in the above reducer, the console.log('here 1') is the last console log being called, meaning that it is definitely returning products ( that is verified in the store state). so im not sure why it is then wiping it in that component?
in that component I call this
componentWillMount = () => {
this.props.showProducts(this.props.chosenCategory.category)
}
which passes in the chosen category so I now what products to render
however, logging this.props in the render method below, is showing it to be an empty array
of course I can post more code if necessary but any reason for this funky behaviour?
extra info:
interestingly when I do this:
default:
console.log('here2');
return [{name: 'prod'}];
}
and then log this.props, it now contains this array with this object???
The store should be immutable, that is, the value you return should be made immutable.
I assume you are adding only a single array in the store
Try changing the reducer like,
const initialState = [];
export default(state = initialState, action) => {
switch(action.type){
case Types.SHOW_PRODUCTS: {
console.log('here1');
let productsToShow = data.filter(category => category.link === action.category)
let newState = [...state,...productsToShow[0].products]
return newState;
}
default:
console.log('here2');
return state;
}
}
I'm trying to implement a method to store in Redux session the counting of results from the base everytime the application also fetches it. The componentWillReceiveProps method is the following:
if (!countingTestAnnouncements && countingTestAnnouncementsSuccess) {
let value = parseInt(totalTests.total);
setCurrentValue(value);
}
It is clear. The method to store in the session will be executed when the counting is successful. This is the action file:
export const SET_CURRENT_VALUE = "hp:alert:set_current_value";
export function setCurrentValue(currentValue) {
return (dispatch) => {
dispatch({
type: SET_CURRENT_VALUE,
payload: currentValue
})
};
}
const ACTION_HANDLERS = {
[SET_CURRENT_VALUE]: (state, action) => {
return {
...state,
currentValue: Value, action.payload
}
}
};
const initialState = {
currentValue: null
};
export default function alertReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type];
return handler ? handler(state, action) : state
};
What is causing me a headache is the fact that when the return is reached within ACTION_HANDLERS, the looping will occur, and I do not know why it is happening.
const ACTION_HANDLERS = {
[SET_CURRENT_VALUE]: (state, action) => {
// By commenting out the return block and putting
// a console.log, the result will be seen only once,
// as expected. But as it is, the loop will happen.
return {
...state,
currentValue: action.payload
}
// console.log(action.payload) will display once the counting.
}
};
Why don't know why componentWillReceiveProps keeps repeating like this. What is really pissing me off is that the whole block is executed, the conditions of the if should be false and do not enter the block.
Whenever you are calling "setCurrentValue(value)" you are triggering and "componentWillReceiveProps" after the dispatch and this cause the loop.
It might be better to get this total in reducer and pass it to store or to reset countingTestAnnouncementsSuccess in order to skip it on next update like given below:
if (!countingTestAnnouncements && countingTestAnnouncementsSuccess) {
let value = parseInt(totalTests.total);
countingTestAnnouncementsSuccess = false;
setCurrentValue(value);
}
So I'm building an application in redux and I ran into the problem outlined in the redux documentation where my view would not update after an action was dispatched. The documentation suggests that this happens when you mutate the state. The only problem is, I don't understand how what I was doing is considered a mutation:
case AppConstants.UPDATE_ALL_TIMERS:
return action.timers
This, however, does work:
case AppConstants.UPDATE_ALL_TIMERS:
let newState = state.map((timer, index) => {
return action.timers[index]
});
return newState
How is it that just returning the value of the action as your new state is considered mutating the state? What am I missing?
Below is the entirety of my reducer, the two cases prior to UPDATE_ALL_TIMERS worked just fine. The reducer is being used with combine reducers and is only receiving the timers slice of state, which is an array of objects.
export default function(state = initialState, action) {
switch(action.type) {
case AppConstants.ADD_TIMER:
let newState = [...state, action.timer];
return newState
case AppConstants.UPDATE_TIMER:
newState = state.map((timer, index) => {
if(index == action.timerID) {
const updatedTimer = {
...timer,
elapsed: action.elapsed
}
return updatedTimer
}
return timer
});
return newState
case AppConstants.UPDATE_ALL_TIMERS:
newState = state.map((timer, index) => {
return action.timers[index]
});
return newState
default:
return state
}
}
Can you post your component ?
Usually when the Store state is alright and well updated yet the component do not re render it's because of deep nested mapped properties.
react-redux do not do deep checks to verify if a value has changed on deep nested mapped objects