componentWillReceiveProps(nextProps) {
if (nextProps.group.data !== this.props.group.data) {
console.log(true);
}
}
the group.data object is comming from
const mapStateToProps = (state, props) => {
return {
group: {
data: state.group.data,
isFetching: state.group.isFetching,
error: state.group.error
}
}
}
I'm using redux module to collect the data...
I know the the nextPros references to this.props and i know a solution will be to use Immutable.js but I couldn't find a good example of using it and I found many complications integrating it, as I have already so many modules without Immutable.
is there any other solution to compare newProps to this.props or a good example how to use Immutable with Redux.
Thanks,
Lucas Franco
This means that the data object from the store isn't changing, you need to make sure that you are returning a new object in the reducer, and never mutate it. For example:
// reducer for group
reducer (state = initialState, action) {
...
// when your code is going to update data return a completely new object
return {...state,
data: { ...state.data, ...action.newData}
// never something like state.data.someProp = action.someProp
}
...
}
I'd need to see more of your code to be more sure, mainly the reducer.
But if the expression (nextPros.group.data !== this.props.group.data) is resulting in true, it's certain to say that the reference to the data object hasn't changed.
Related
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.
Problem - my component is updating even if the input data hasn't changed and I suspect it's because an object ref change while a deep compare didn't change.
I have a reducer like so:
const getDefaultState = () => ({
mainNotifMessage: '(unknown message)',
notifDetails: '(unknown notification details)',
snackBarOpen: true
});
export type CPAction = {type: string, val: object}
export const notifsReducer = (state = getDefaultState(), action: CPAction) => {
switch (action.type) {
case c.NOTIFY_ERROR:
return {
...state,
...action.val
};
default:
return {
...state
};
}
};
and then in an component I have:
function mapStateToProps(state: RootState) {
return {
notifs: state.notifs
};
}
I think what happens is that state.notif always gets a new object reference, so React/Redux is always updating my component even if there are no changes. But if I change it to this:
function mapStateToProps(state: RootState) {
return {
mainNotifMessage: state.notifs.mainNotifMessage,
snackBarOpen: state.notifs.snackBarOpen,
severity: state.notifs.severity,
notifDetails: state.notifs.notifDetails
};
}
then it doesn't update the components. Is there a way to tell Redux/React to always to do a deep compare and not return early on object reference differences?
I think what happens is that state.notif always gets a new object
reference, so React/Redux is always updating my component even if
there are no changes.
By default, React-Redux decides whether the contents of the object returned from mapStateToProps are different using === comparison (a "shallow equality" check) on each fields of the returned object.
default:
return {
...state
};
You are returning a new reference each time in your default state.
So when you read values using dot notation, you get the same values.
But for Javascript, it is a new object reference, hence React-Redux will try to re-render the component.
You can just do return state instead of spreading it and creating a new object reference in the default case of your reducer.
Reference : https://react-redux.js.org/using-react-redux/connect-mapstate#return-values-determine-if-your-component-re-renders
EDIT: The solution is to return state after I replace it completely (return state = {...action.payload})! But why? I don't need to return it when I replace the fields individually.
I'm working with the Redux Toolkit, which simplifies some Redux boilerplate. One thing they do is use Immer to allow you to directly 'modify' state (in fact, you're not). It works fine, except I don't know how to replace my section of state entirely. For instance, I want to do something like this
const reducer = createReducer({ s: '', blip: [] }, {
[postsBogus.type]: (state, action) => {
state = { ...action.payload };
}
but state remains untouched. Instead I have to do this
[postsBogus.type]: (state, action) => {
state.s = action.payload.s;
state.blip = action.payload.blip;
}
Is there a way I can replace state entirely?
Yes, as you noted, you must return a new value to replace the state entirely.
Even in a "plain" Redux reducer, assigning state = newValue does nothing, because all that does is say that the local function variable named state is now pointing to a different value in memory. That does nothing to return a new value.
For Immer specifically, you can either mutate the contents of the Proxy-wrapped state value as long as it's an object or array, or you can return an entirely new value, but not both at once.
You can, but not in this way, when you say:
function x(y){
y = 4
}
You're mutating the function parameter, but not the state of redux,
you have two options to update this state of your redux store:
either to set state.your_state_name = something or, in your case, what you want to do is to return a new object, the new object is what the new state value is going to be.
simple example:
myReducerFunc: (state, action) => {
return {...action.payload }
},
another example:
const loggedInUserSlice = createSlice({
name: '$loggedInUser',
initialState: {
isLoggedIn: false,
},
reducers: {
loggedIn: (state, action) => {
return {
isLoggedIn: true,
...action.payload,
}
},
},
})
Please correct me if I'm wrong, because I've heard different stories with using redux with react. I've heard that you should have all your logic in your reducers, I've also heard that the store should be your single source of truth.
That said, I'm taking the approach where my logic, that is filtering stuff from json file is in a reducer file. Then I call the actions to filter out different parts of the json and return it,
import Data from "./BookOfBusiness.json";
const initialState = {
TotalData: Data
};
const filterDataWTF = (state = initialState, action) => {
let newState = { ...state };
let itExport = newState.TotalData;
if (action.type === "Status55") {
let itExport22 = {...itExport};
console.log("came to Status55 and itExport value is " + itExport22); // comes undefined. WHY??
return itExport22.filter(xyz => {
if (xyz.Status55.includes("Submitted")) {
return true;
}
return false;
});
}
return itExport;
};
export default filterDataWTF;
the problem is my variable itExport22 is showing up as undefined. Any tips please. TYVM
Okay once we got something working that you can vote for an answer, lets write an answer for you.
In that case your filter is returning a boolean. You should do something like so:
return itExport22.filter(xyz => xyz.Status55.includes("Submitted"));
Returning an empty array you are sure that you are receiving the same type on your component.
Your filter is fine. Only this should work
Your reducer should always return the new state
your reducer return type is boolean or itExport22 but it's should be object contains TotalData
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