Redux reducer prevState already set up to newState - reactjs

I am using redux to move search filter through application to components.
I have component where I set up filters and call dispatch function using
this.props.dispatch(setFilter(newFilter));
I use sliders / range components to set up values, those components have handleAfterChange method which is set up to call mentioned dispatch function. This is working fine.
I have also filters that are set up by clicking buttons, I created onClick handler, and this handler call mentioned function. I checked what I am sending to setFilter functions (newFilter) parameter and it is what I want to set up.
My set action is defined:
export const setFilter = (filter = {}) => {
return {
type: SET_FILTER,
filter
};
};
My reducer is:
const searchFilter = (prevState = INITIAL_STATE.filter, action) => {
switch (action.type) {
case SET_FILTER: {
// prevState is already the state
// I want to set up in this reducer
console.log(prevState);
return Object.assign({}, prevState, action.filter);
}
case RESET_FILTER: {
return INITIAL_STATE.filter;
}
default: {
return prevState;
}
}
};
Problem is that prevState is already the object I want to set up in reducer.
I am checking (in another component) if filter has been changed and because of that I get prevProps and nextProps the same so no action will be triggered.
So somehow dispatched changes are already in reducer as prevProps.

In searchFilter reducer,you are returning the prevState as it is without mutating.
return prevState //which is reference to prevState = INITIAL_STATE.filter
correct way (no sure about state structure),
default: {
return {...prevState};
}
OR
return Object.assign({}, prevState);

#RIYAJKHAN was partially right.
Somehow I was modifying redux store directly, even if I made all object modifications via Object.assign(...).
In the end I had to create copy of object I was modifying - JSON.parse(JSON.stringify(filter)) and then update this new object and dispatch it to redux store

Related

Immutability in react redux reducer

If I write my reducer in the following way, then the "render" method is getting called and its a expected behavior. No problem here :
const initState = {
entries: ["test1"]
};
export const EntryReducer = (state = initState, action) => {
switch (action.type) {
case ADD_ENTRY:
return Object.assign({}, state, {
entries: state.entries.concat("test2")
});
break;
case DELETE_ENTRY:
break;
}
return state;
}
But, if I write the reducer in the following way, then the "render" method is not getting called though state is being updated :
export const EntryReducer = (state = initState, action) => {
let newState = Object.assign({}, state);
switch (action.type) {
case ADD_ENTRY:
newState.entries.push("test2");
break;
case DELETE_ENTRY:
break;
}
return newState;
}
I could not understand why render is not getting called. As far as I understand, "newState" is a immutable object and does not hold any reference to the "state" object.
Please help me understand it. Thanks.
Because Object.assign is shallow, it will not create a new Array, the old one will be mutated by .push():
state.entries === newState.entries // ["test1", "test2"]
"newState" is a immutable object
If you don't do that on your own, it's ordinary object/array/etc so it is not immutable.
why render is not getting called
React-redux tries its best and actually wraps your component into PureComponent. It does that to make all your connect()-ed components will not re-render on any action called but only once store has been updated in relevant places.
To realize if relevant data has been changed or not, PureComponent compares shallowly like oldDataProp !== newDataProp. Once you mutate objects/arrays that check will fail so component will not re-render.
It has nothing to do with the way you update the state. Consider it correct, though I would not recommend to avoid common best practices.
In your case render is not called because component props is not changed...
I believe that you may have something like this:
<Component entries={this.props.entries}/>
and
mapStateToProps = (state) => ({
entries: state.entries
})
If it is so, then state.entries is the prop that controls whether your component will be re-rendered or not. If it has the same value during the state change on ADD_ENTRY action - the component will not be re-rendered.
So. Get back to roots. Remember that in JavaScript state.entries is a POINTER, that points to array in memory.
While you calling entries.push the array in memory will be extended with another element - thats for sure. BUT the POINTER value that we have in state.entries will remain the same. It will be not changed. Thats how Array.push works.
As the result <Component entries={this.props.entries}/> will not be re-rendered.
By changing newState.entries.push("test2");
to
newState.entries = newState.entries.concat("test2");
you actually changing entries pointer, component sees that property props.entries has changed and now the whole thing re-renders...
You need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
You should use a Deep clone.
let newState= JSON.parse(JSON.stringify(state));

componentWillReceiveProps not called after redux dispatch

I'm building a react native app and using redux to handle the state. I am running into a situation where one of my containers is not updating immediately when the redux state is changed.
Container:
...
class ContainerClass extends Component<Props, State> {
...
componentWillReceiveProps(nextProps: Object) {
console.log('WILL RECEIVE PROPS:', nextProps);
}
...
render() {
const { data } = this.props;
return <SubComponent data={data} />
}
}
const mapStateToProps = (state) => ({
data: state.data
};
export default connect(mapStateToProps)(ContainerClass);
Reducer:
...
export default function reducer(state = initalState, action) => {
switch(action.type) {
case getType(actions.actionOne):
console.log('SETTING THE STATE');
return { ...state, data: action.payload };
...
...
...
In a different random component, I am dispatching a call with the actionOne action, which I confirm prints out the relevant console.log. However, the console.log in the componentWillReceiveProps in the container is not printed.
The component that dispatches the call is a modal that has appeared over the Container, and closes automatically after the call is dispatched and the state is updated. What is weird is that although the Container isn't updated immediately, if I navigate to a different page and then back to the Container page, the state is in fact updated.
EDIT: Initial state is:
const initialState: Store = {
data: []
}
And the way I dispatch is in a different component which gets called as a new modal (using react-native-navigation) from Container:
fnc() {
...
setData(data.concat(newDatum));
...
}
Where setData and data are the redux dispatch action and the part of the store respectively that is passed in on props from the Container (which has setData and data through mapStateToProps shown above and a mapDispatchToProps which I didn't show).
I solved my problem by updating from react-native v0.56 to v0.57. Apparently there was a problem with react-redux v6 working properly in the react-native v0.56 environment.
Assuming you're using a recent version of React, componentWillReceiveProps is actually deprecated:
Using this lifecycle method often leads to bugs and inconsistencies
You can't really rely on that lifecycle hook in a number of situations. You may want to look at a slightly different approach with componentDidUpdate instead.
I think more important is to get the value after changing in state of redux rather than in which lifecycle you are getting the value . so for getting the value you can use subscribe method of redux in componentDidMount
store.subscribe( ()=> {
var updatedStoreState = store.getState();
})
I believe that getDerivedStateForProps would solve your problem.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.data !== prevState.data) {
//Do something
} else {
//Do something else
}
}
You would check the state from the redux against the state from your component and then act accordingly.
Also, some info from the documentation that you might consider before using this method:
1. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
2. This method exists for rare use cases where the state depends on changes in props over time.
3. If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
You can read more at: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

Updating string in redux store

I am using redux with react. I have a key in store whose value is a string.
This is what is get in store.getState();
{appBgColor: "#FFF"}
I am updating the store like this.
store.dispatch( changeAppBgColor("#FC0") );
Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_APP_BG:
return { ...state, appBgColor: action.payload};
default:
return state;
}
};
Everything works fine, the appBgColor get changed whenever a new dispatch happens.
Problem
I have read that to change a key in redux store you should use concat, slice or join to make the state immutable. So I doubt I am doing it wrong. Is this the correct way to do it?
For your use-case of updating the field appBgColor within your state, your reducer implementation is correct.
Note that when you return { ...state, appBgColor: action.payload}, you are not mutating the state, but in fact, creating a copy of the existing state, applying the change and returning it. This makes sure that the state is immutable, ie it is not directly modified.
You would only need to use functions like slice, concat etc when you are updating nested items within your state. For eg, when you need to remove an element from an array within your state, you would need to use slice as shown below.
const index = state.findIndex(a => a.id === action.id)
return [
...state.slice(0, index), ...state.slice(index + 1)
]

redux: re-render component after async fetch & document creation

I have a component that upon componentDidMount will perform an async API database fetch.
On the same component the user can add an item to the collection. Unfortunately, the component does not automatically re-render, even though the new document was added to the collection. How do I invoke another render with the new document showing as well?
If your collection is an array, when an item was created you should return a new array by using concat method instead of push or access directly into the array via the index.
See this code:
const myReducer = (state = initState, action) => {
switch (action.type) {
case 'FETCH_SUCCESSFUL': return action.data
case 'NEW_ITEM': return [].concat(state, [action.newItem])
}
}
When you return a new array, redux will know the state was changed and your component will also update automatically.

Can I bind store's state with a component in react-redux?

I am Newbie in react-redux, I created a small CRUD application with react-redux, when I click over add button from a child component it takes data from all input boxes and updates my store's array with the help of Reducer.
const initialState = {
itemObjectArr:[] }
My Reducer updates this itemObjectArr, and send it to needy component with the help of connect(mapStateToProps)(NeedyChildComponent) function.
Now the real problem occurs when I try to print state.itemObjectArr on the console.
const mapStateToProps = (state)=>{
console.log(state.itemObjectArr);
return {itemObject:state.itemObjectArrs}}
Above code gives an error on console TypeError: Cannot read property 'itemObjectArrs' of undefined
and if I do console.log(state) it prints objects contained array easily without any error.
I searched for this error but not get anything important, I think this store's state is immutable and when it passes to mapStateToProps function as an argument then mapStateToProps checks changes in state and if it does not get any changes then it returns an error. maybe this is occurring here.
What does your reducer look like? Since the store is returned as undefined in your component, it sounds to me that you haven't defined a default case in your switch statement that returns the initial state before your update action is executed, like:
const myReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_ITEM_OBJECT_ARR': return { itemObjectArr: [ 1, 2, 3] }
default:
return initialState;
}
}

Resources