Reducer and store state not in sync - javascript

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;
}
}

Related

Default case returns mutated state redux

I am building a ETH portfolio tracker using ethplorer's API with React, redux-react and thunk middleware on the frontend. The main component of the store is an array of objects (tokens). You could see its reducer and actions below:
import {createToken, initializeTokens, deleteToken, replaceTokens} from '../services/tokens'
const tokenReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TOKEN':
return state.concat(action.data)
case 'ERASE_TOKEN':
return state.filter(token => token.address !== action.data)
case 'INIT_TOKENS':
return action.data
default:
return state
}
}
//Defining Actions
export const addToken = address => {
return async dispatch => {
const newToken = await createToken(address)
dispatch({
type: 'ADD_TOKEN',
data: newToken
})
}
}
export const updateTokens = () => {
return async dispatch => {
const updatedTokens = await initializeTokens()
await replaceTokens(updatedTokens)
dispatch({
type: 'INIT_TOKENS',
data: updatedTokens
})
}
}
export const eraseToken = address => {
return async dispatch => {
await deleteToken(address)
dispatch({
type: 'ERASE_TOKEN',
data: address
})
}
}
export default tokenReducer
Imported functions are being used to fetch tokens from the API and then save them into a local database. Another component of an action is a MarketCap filter (string) that I use to sort tokens according to their market cap (biggest to smallest, smallest to biggest or none). It has a super simple reducer and an action:
const mcReducer = (state='NONE', action) => {
switch (action.type) {
case 'SET_MC_FILTER':
return action.filter
default:
return state
}
}
export const mcFilterChange = filter => {
return {
type: 'SET_MC_FILTER',
filter
}
}
export default mcReducer
The problem begins when I start sorting tokens for display in the React component. While I do not intend to change the tokens state, and only want to change the array of displayed tokens, my tokens state neverthless changes to the sorted out one after I sort by the MC. So what happens is: programm dispatches SET_MC_FILTER => TokenList sorts tokens according to the filter => tokenReducer returns mutated state instead of the old one. I don't understand why it happens, since I don't dispatch any actions that should affect the tokens state here and by default tokenReducer should just return the state that was stored in it. Here is the last piece of code where the problem apparently happens:
const initTokens = useSelector(state => state.tokens)
const mcFilter = useSelector(state => state.mcFilter)
const getDisplayTokens = inTokens => {
switch (mcFilter) {
case 'NONE':
return inTokens
case 'DESCENDING':
return inTokens.sort(compareMCDescending)
case 'ASCENDING':
return inTokens.sort(compareMCAscending)
default:
return inTokens
}}
return(
<div className='token-list'>
{getDisplayTokens(initTokens).map(t =>
<TokenTile token={t} key={t.id}/>
)}
</div>
)
I have tried to track down the exact place where the tokens change with debugger and trace option of redux-devtools, but everywhere the tokenReducer instantly returns the changed state, and I have no idea why. Any bits of help would be greatly appreciated
Array.prototype.sort() mutates arrays in place. You should never try to call .sort() directly on arrays that were read from the Redux state. You must make copies of the arrays and then sort them.
Also, note that you should use our official Redux Toolkit package, which will both eliminate mutations in reducers, and throw errors if you ever try to mutate code outside of reducers.
See https://redux.js.org/tutorials/fundamentals/part-8-modern-redux for a tutorial on how to use RTK correctly.

how spread syntax in the reducer is different from just returning the array coming from the action?

I can not understand that part, i know that if i have an array like this:
const x = [1, 2, 3, 4]
should be the same like this:
const y = [...x];
How this can be different in redux :
I have an action like this that takes a product and cartProducts(the previous state) and check if the product exist in the cartProducts array (by comparing ids), if the product does not exist push it to the cart, if exist increment the quantity of the found product:
This is my action
export const addToCart = (product, cartProducts) => {
if (!product.quantity) {
product.quantity = 1;
}
const index = cartProducts.findIndex(p => p.id === product.id);
if (index < 0) {
cartProducts.push(product);
}
else {
cartProducts[index] = {
...cartProducts[index],
quantity: cartProducts[index].quantity + 1
}
}
return {
type: ADD_TO_CART,
payload: cartProducts
}
}
The reducer that work:
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return [...action.payload];
default:
return state;
}
}
The reducer that does not work(cartProducts does not render properly and don't know why??):
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return action.payload;
default:
return state;
}
}
How spread syntax changed everything here, the same array with same products should be returned in both cases??
Because spread syntax is creating a new array so the subscriber knows it changed. Returning the modified original array, the subscribers won’t know it changed.
Arrays (and objects) are passed by their reference, so if you return a reference to the same array, regardless of what you inserted or removed, javascript thinks it is the same array. This doesn't work with a reducer because, by design, subscribers to the store will only react to changes, so they'll only react if the array actually changes, and the only way to do that is to return a different (new) array.
This is why it's generally advisable to avoid mutating arrays or objects in this kind of store framework (or in general), and treat objects and arrays as immutable.

Returning a payload value to Redux session is causing an infinite loop

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);
}

Does a slice of react redux store state have to be an object?

I'm combining the slice of state below (filterText), it's use to filter out results so it only needs to hold a string. Is it possible to just have the initial state be an empty string? Or does it have to be an object even though it's just a slice of the overall larger store object? If I can have it as a string, how do I make a new copy of the state each for each dispatch? The current return {...state, ...action.data} looks weird.
const initialState = ''
const filterText = (state = initialState, action) => {
switch (action.type) {
case constants.FILTER_CONTACTS:
return {
...state,
...action.data
}
default:
return state
}
}
export default filterText
The initial state can be a string, but then in every switch case it should also return a string.
When updating the state, you would not need to make a copy since your entire state is a string you would just return the new string. If there are no changes, you would just return the old state.
const initialState = ''
const filterText = (state = initialState, action) => {
switch (action.type) {
case constants.FILTER_CONTACTS:
// return a string here, I'm assuming action.data is a string
return action.data;
default:
return state
}
}
export default filterText
Hope this helps.

Why does this reducer not trigger a view change using redux?

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

Categories