React componentWillReceiveProps will receive props not triggering when redux state updates - reactjs

I've got a store which holds actions, these actions are suppose to be looped through each time a new one is added.
I've got a component MessageListView which resides in a parent called MessageView. When a new action is added to my socketActions.queue array, the componentWillRecieveProps is suppose to trigger, but it doesn't.
Here is what my reducer looks like:
/* Reducer */
const initialState = {
queue: [{
type: 'alert',
action: 'alert',
completed: false, // Set to true or just delete this object when the action has been completed
data: {
message: "the data object dynamically changes based on the type"
}
}]
};
export default (state = initialState, action) => {
switch (action.type) {
case ADD_ACTION:
let queue = state.queue
// action.data['completed'] = false
queue.push(action.data)
console.log("Just updated queue", action.data)
return {
...state,
queue: queue,
latestAction: new Date()
}
My component is connected up to the store like this:
function mapStateToProps(state){
console.log(state, "State inside messagelistview.js")
return {
actionsQueue: state.socketActions.queue,
latestAction: state.socketActions.latestAction
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ completeAction }, dispatch);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MessageListView);
So when I dispatch a new ADD_ACTION action, my state updates & redux-logger prints out the new and old state which is telling me that they're the same?! . I don't know why it'd be doing this after I've changed the latestAction value & queue array. This is why the componentWillRecieveProps isn't working, but I can't figure out why the state is the same?!

Not 100% certain if this would solve anything, but I think you aren't properly copying over state.queue into the completely new variable queue.
I'd suggest doing something like:
let queue = state.queue.slice()
... and see if anything changes? Right now your queue variable is still the same as the state.queue

You don't change queue identity in reducer. Try this code:
case ADD_ACTION:
let queue = state.queue
// action.data['completed'] = false
queue.push(action.data)
console.log("Just updated queue", action.data)
return {
...state,
queue: [...queue], // <-- here we copy array to get new identity
latestAction: new Date()
}
You should always shallow copy changed objets. See http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments

A connected component does a shallow check (===) whether the state is updated and renders wrapped component only if that check fails. In your case, you're mutating your queue causing the equality the check to pass.
It will work if you change your reducer like this:
state.queue = state.queue.concat(action.data);
or with ES6 syntax:
state = { ...state, queue: [...state.queue, action.data] };

The problem is that you are mutating your state when you do this:
let queue = state.queue
// action.data['completed'] = false
queue.push(action.data)
When you mutate the existing state directly, Redux doesn't detect a difference in state and won't notify your components that the store has changed.
So instead you've the following options to create a new queue array:
case ADD_ACTION:
return {
...state,
queue: state.queue.concat(action.data),
latestAction: new Date()
}
or with ES6 sugar:
case ADD_ACTION:
return {
...state,
queue: [...state.queue, action.data]
latestAction: new Date()
}

Related

Redux Toolkit - I can't update Slice state?

I wanna update the state and tried few ways to do that, but I can't.
First, I got a problem with fetching state, as a result, I got proxy, not a state.
That is fixed by the current() function by the redux toolkit.
Next, where I have a problem is mutation main slice state.
Here is a reducer for mutating.
reducers: {
setUser: (state, payload) => {
console.log("before", current(state)); //init state
state.currentUser = {
...state.currentUser,
loggined: true,
};
console.log("after", current(state)); // here I have new state but...
},
clearUser: (state) => {},
},
As a result, as a second console log, I got to state what I want, when I want to change a page or just want to do something with new data in the state, by useSelector() redux function, as a result, I got old, not changed state.
Why has this happened?
Example of Slice state:
initialState: {
currentUser: {
loggined: false,
isAdmin: false,
jwt: false,
},
},
Thanks!
Reducers of createSlice use immer:
This object will be passed to createReducer, so the reducers may safely "mutate" the state they are given.
So you can either return a new object that is the new state or "mutate" it and not return it, from createReducer
you need to ensure that you either mutate the state argument or return a new state, but not both.
So you can do:
setUser: (state, payload) => {
//state here is an immer draft, do not use that to copy current state
console.log("before", current(state)); //init state
state.currentUser.loggined = true;
//not returning anyting
},
Not sure how you'd return a new state based on the old one since ...state makes a copy of the immer draft and not of the state. Can't even find examples of doing this unless it's an array.

Redux-Saga using Immutable.js and state is always empty

I am trying to wrap my head around redux and sagas and I think I have set something up wrong and i'm hoping someone can lend some insight.
I have created my store with my inital state and I dispatch an action, as seen here:
const initialState = fromJS({
product: {},
basket: {},
global: {}
});
const reducers = combineReducers({ product, basket, global });
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers,
initialState,
applyMiddleware(sagaMiddleware))
initSagas(sagaMiddleware);
store.dispatch(retrieveSiteKeyValues())
return store;
};
Combine Reducers is from redux-immutable.
My saga function:
export function* fetchSiteKeyValuesSaga() {
yield take(RETRIEVE_SITE_KEY_VALUES)
const siteKeyValues = yield call(retrieveSiteKeyValues)
yield put(storeSiteKeyValues(siteKeyValues));
}
My reducer function:
const storeSiteKeyValues = (state, payload) => {
payload.anotherObject = {};
payload.anotherMap = new Map();
const newState = fromJS({ payload })
return newState
// OR return state.merge({ global: { siteKey: action.siteKey } }); ?
}
When I interrogate the state object the size is zero. I expected the size to be at least 3 due to my initalState. When the newState is 'created' the size is 4. But when it drops back into the state switch statement, the state size is zero again:
export default (state, action) => {
switch (action.type) {
case STORE_SITE_KEY_VALUES : {
return storeSiteKeyValues (state, action.payload);
}
default:
return state;
}
}
Im 90% sure just dumping over the state as I am doing in the reducer function is wrong and i should be using set() or setIn() i thought update() would make more sense, but when I use those methods the state is always empty or 'undefined' if I try to enact .get(x) in the console.
When I inspect the state in the browser it looks like this:
storeState:{
[2],
[2]
[2]
}
The array expanded looks like this:
0:"product"
1:{anotherObject :{}, anotherMap: map()
size:1
I would expect the values that were part of of the payload to be here not just the new object and map.
Am I initaiting my state incorrectly at the store creation? Am I approaching redux and state management in the wrong way?
I want to be sure you aren't missing a fundamental part: where is the sagaMiddleware.run(YOUR_SAGA); call? Is it hidden inside initSagas?
It was setting my inital state twice, once when I was initialsing my store and again when the reducer inital state was read. My state in my reducer was an empty object as it would be if on the time of reducer 'activation'. In the end I realised i'm not reading some 'remembered' state from anywhere, I just needed some inital values. Which I moved into the reducer and remvoed the immutable js from my app as it was confusing matters.
Some lessons for all you newbies to react/redux-saga! Don't try and over complicate matters. Learn what immutable mean! Figure out for yourself if you need it, in my case just having one source of truth and access to state was enough.
Further reading:
Initalising State ,
Immutable and State considerations

How to use redux saga in editable table efficiently

I have a multi page react application in which one endpoint has to show data in tabular form. Show I take GET_INFO action on componentWillMount of that endpoint. Now I have a reducer called table_info which has table_data array and shouldTableUpdate boolean in it.
My table is editable with edit and delete icon in every row. I am facing problem in update, on update I call reducer with action UPDATE_TABLE_ROW and if success than I do something like following :
//reducer.js
const initialState = {
table_data:{}, shouldTableUpdate:false;
}
export default function myReducer(state=initialState, action){
switch(action.type){
case UPDATE_SUCCESS:
// how to handle edited row here?
// also when I print my state of this reducer
// state becomes nested, so if one does lots of updates
// it will be become very heavy...
return {...state, shouldTableUpdate:true}
}
}
Can you tell how to handle update, delete, add on table using redux saga efficiently ? On googling I get naive examples only, so came to SO.
Note: Can't show the actual code as it's for my company project. Sorry for that.
Thanks.
Can you tell how to handle update, delete, add on table using redux saga efficiently ?
Well you can plainly manipulate the state object using a reducer only.
Comments:
table_data is a list and not an object.
I don't think you'll be needing shouldTableUpdate since state change in store will trigger a component update if state field is mapped in mapStateToProps.
So here's a basic template of adding, updating and deleting items via reducer.
const initialState = {
table_data: [],
};
export default function myReducer(state=initialState, action){
switch(action.type) {
case ADD_ITEM:
return {
...state,
table_data: [
...state.table_data,
action.item, // item to be added
]
};
case UPDATE_ITEM:
let updatedItem = action.item;
// do something with updatedItem
return {
...state,
table_data: table_data.map(e => (
e.id === updatedItem.id ? updatedItem : e
)),
};
case DELETE_ITEM:
const index = state.table_data.findIndex(e => e.id === action.item.id);
const numItems = state.table_data.length;
return {
...state,
table_data: [
// exclude index
...table_data.slice(0, index),
...table_data.slice(index+1, numItems),
]
};
default:
return state;
}
}

Redux action taking too long add one item to empty array - No Promise returned

I am using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.

Changing state in Redux

I am trying to add an element to an array in the state and change another array element's property. Say we have the following state structure:
{
menuItems: [{
href: '/',
active: true
}]
}
After dispatching the ADD_MENU_ITEM action I want to end up with this state:
{
menuItems: [{
href: '/new',
active: true
}, {
href: '/',
active: false,
}]
}
I have tried managing this in Redux reducers in several fashions:
function reducer(state = {}, action) {
switch (action.type) {
case ADD_MENU_ITEM: {
let menuItems = state.menuItems;
let newMenuItem = action.newMenuItem;
// First try
menuItems[0].active = false;
menuItems.unshift(newMenuItem);
state = Object.assign({}, state, { menuItems: menuItems });
// Second try
menuItems[0].active = false;
menuItems.unshift(newMenuItem);
state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});
// Third try
menuItems[0].active = false;
state = (Object.assign({}, state, {
menuItems: [
Object.assign({}, newMenuItem),
...menuItems
]
}));
// Fourth try
menuItems[0].active = false;
state = update(state, {
menuItems: {$unshift: new Array(newMenuItem)}
});
console.log(state);
return state;
}
}
}
In the fourth try, I am using React's Immutability Helpers but it never works. I logged the state to the console before returning the state and it logs correctly, but when logging inside the components which get re-rendered, the menuItems array does not add the first item, although the active member is set to false.
What could I be doing wrong?
The state in the reducer should be immutable and for this reason should not be modified. It is also recommended to flatten your object whenever possible.
In your scenario your initial state could be an array as such:
[{
href: '/',
active: true
}]
In your reducer, try returning a brand new array as follows:
function reducer(state = {}, action) {
switch (action.type) {
case ADD_MENU_ITEM: {
return [
action.newMenuItem,
...state.map(item => Object.assign({}, item, { active: false }))
];
}
}
}
More information about reducers can be found here: Redux Reducers Documentation
Helpful excerpt from the documentation:
It’s very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Calling non-pure functions, e.g. Date.now() or Math.random().
MORE INFO ADDED
In your reducer, and for all four tries, you are modifying the existing state before returning it.
This results in react-redux, when checking if your state has changed, not to see any changes as both the previous and next states are pointing to the same object.
Here are the lines I am referring to:
First Try:
// This line modifies the existing state.
state = Object.assign({}, state, { menuItems: menuItems });
Second Try:
// This line modifies the existing state.
state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});
Third Try:
// This line modifies the existing state.
state = (Object.assign({}, state, {
menuItems: [
Object.assign({}, newMenuItem),
...menuItems
]
}));
Fourth Try:
// This line modifies the existing state.
state = update(state, {
menuItems: {$unshift: new Array(newMenuItem)}
});

Resources