Redux reducer weird behavior? - redux

Hey guys i wonder why my state is getting undefined after I process through this :
Just want to decrease the like property from 1 do you guys see issues in my nested processing ?
case "UNLIKE_COMMENT":
const unLikeIndex = getKeyIndex(
state.list[action.index],
action.commentId
);
return updateObject(state, {
isFetching: false,
list: {
...state.list,
[action.index]: updateValue(unLikeIndex, state.list[action.index], {
[unLikeIndex]: {
...state.list[action.index][unLikeIndex],
like: state.list[action.index][unLikeIndex].like - 1,
},
}),
},
});
export const updateObject = (oldObject, newValues) =>
Object.assign({}, oldObject, newValues);
export const updateValue = (index, state, value) =>
state.map((item, i) => {
i === index ? value : item;
});
// Tested and works
export const getKeyIndex = (array, keyToFind) => {
let ret = -1;
array.forEach(({ key }, index) => {
if (key === keyToFind) ret = index;
});
return ret;
};
Before / After processing

Honestly I am finding your code incredibly hard to read and there may be more errors than just this one. But for sure your updateValue function is mapping the array to undefined.
export const updateValue = (index, state, value) =>
state.map((item, i) => {
i === index ? value : item;
});
The problem is that you put your map callback inside of curly braces {} without a return statement. map replaces every element with the returned value, so it replaces every element of the array with undefined.
You can either add the word return
updateValue = (index, state, value) =>
state.map((item, i) => {
return i === index ? value : item;
});
Or write it inline without the curly braces
updateValue = (index, state, value) =>
state.map(
(item, i) => i === index ? value : item
);

Related

Why is using 'push' inside createSlice add '3' to my state instead of appending an array?

I am trying to append action.payload to my state. However, push methods adds action.payload.length to my state instead of appending the entire array! What am I doing wrong?
const initialState = { users: [] };
export const usersSlice = createSlice({
//other code.
,
extraReducers(builder) {
builder.addCase(fetchUsers.fulfilled, (state, action) => {
console.log(current(state));
state.users = state.users.push(...action.payload);
console.log(action.payload);
console.log(current(state));
// this one works.
// state.users = state.users.concat(action.payload);
});
},
});
// selector
export const selectUserById = (state, userId) =>
state.users.users.find((user) => user.id === userId);
This is the error I get (referring to selector):
TypeError: state.users.users.find is not a function
And this is my console:
// This is my state.
{
"users": []
}
// this is action.payload. Which is an array of 3 objects.
[
{
obj1
},
{
obj2
},
{
obj3
}
]
// This is the state after using push:
{
"users": 3
}
Well, such a silly mistake.
As y'all know, push method does not return anything. Thus, it made no sense for me to try to assign it to my state. concat method on the other hand, returns a new array. That's why it worked.
Here is what I changed:
state.users.push(...action.payload);
There is not state.users = anymore.

Selector returns empty array from the redux state, even though the array has values

I have the following normalized redux state:
rootReducer: {
blocks: {
"key1": {
id: "key1",
beverages: [], // Array of objects
}
}
}
and I'm trying to select the value of beverages for beverage with the id of "key1" using this selector:
export const getBlockBeverages = (state, blockId) => {
console.log("selector", state.blocks[blockId].beverages);
return state.blocks[blockId].beverages;
};
Whenever I add a new beverage into the beverages array, the selector gets called twice, first time with an empty array, second time with proper values:
Initial state
selector []
selector []
Adding new beverage:
selector []
selector [{/*beverage1*/}]
// Adding another beverage
selector []
selector [{/*beverage1*/}, {/*beverage2*/}]
I'd really appreciate any help/explanation why does the selector get called and beverages value for the block instance is an empty array.
Below is the code for reducers I'm using - I don't see where I could be mutating the original state, I used Immer's produce from the beginning and the problem is still present. Then I tried to use lodash.clonedeep to make sure that I return a new state, but the selector still logs that empty array.
const blockReducer = (state = { id: "", beverages: [] }, action) => {
if (action.type === ADD_BEVERAGE_TO_BLOCK) {
const { beverageId } = action.payload;
const newBeverage = { id: uuid4(), beverageId };
return produce(state, (draft) => {
draft.beverages.push(newBeverage);
});
}
return state;
};
const blocks = (state = {}, action) => {
const key = action.payload.key;
if (key && (state[key] || action.type === CREATE_BLOCK)) {
const instanceState = blockReducer(state[key], action);
return produce(state, (draft: any) => {
draft[key] = instanceState;
});
}
return state;
};
Any ideas why the selector returns empty array instead of array of length 0, 1, 2 etc. as I'm adding new beverages? I'm clueless and will appreciate any help.
The problem was in a different selector that I had been using in a wrong way.
export const makeGetBlockBeveragesLength = () => createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
and instead of mapStateToProps I used createMapStateToProps:
const createMapStateToProps = (state, { blockId }) => () => {
const getBlockBeveragesLength = makeGetBlockBeveragesLength();
return {
length: getBlockBeveragesLength(state, blockId),
};
};
export const Component = connect(createMapStateToProps)(MyComponent);
The empty array logged in one of the logs refers to an older state (the initial state in this case).
I fixed the code to this and it works:
export const getBlockBeveragesLength = createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
const mapStateToProps = (state, { blockId }) => ({
length: getBlockBeveragesLength(state, blockId),
});
export const Component = connect(mapStateToProps)(MyComponent);

Redux dispatch action

I am just trying to run a simple redux program when i use command node index it shows me error that action must be plain objects below is my code for that
const redux = require('redux')
const createStore = redux.createStore
const BUY_CAKE = 'BUY_CAKE'
function buyCake () {
return
{
type: BUY_CAKE
}
}
const initialState = {
numOfCakes: 10
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case BUY_CAKE: return {
...state,
numOfCakes: state.numOfCakes - 1
}
default: return state
}
}
const store = createStore(reducer)
console.log("initial state is ", store.getState())
const unsubscribe = store.subscribe(() => console.log("updated", store.getState()))
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
unsubscribe()
when i dispatch(buyCake()) then only it shows error but if i do store.dispatch({type:BUY_CAKE}) then code runs fine why is the error occuring
Because your return statement is wrongly formatted. Be aware of this deadly feature:
JavaScript will automatically insert semicolons. Without the parentheses, JavaScript would ignore the following lines and return without a value.
This is your function with semicolons, which will return undefined:
function buyCake(){
return;
{
type:BUY_CAKE
};
};
Solution: Move your curly brackets to the return line:
function buyCake(){
return {
type: BUY_CAKE
}
}

How would you write the condition in ramda?

I'm new to Ramda and just trying to wrap my head around it. So here is the function I want to rewrite in functional style:
const makeReducer = (state, action) => {
if (action.type === LOG_OUT) {
return rootReducer(undefined, action)
}
return rootReducer(state, action)
}
Here is what I end up with:
const isAction = type => R.compose(R.equals(type), R.prop('type'))
const makeReducer = (state, action) => {
const isLogOut = isAction(LOG_OUT)
return R.ifElse(isLogOut, rootReducer(undefined, action), rootReducer(state, action))(action)
}
I assume it's totally wrong as there are several duplications of action and rootReducer
Actually I don't see any reason to refactor your code: you're not mutating inputs and you use if to conditionally return outputs.
About rootReducer(undefined, action), I believe that you should use parameter destructuring:
const rootReducer = ({ state, action } = {}} => {
// Stuff here
}
That is, you may give either state or action, or both:
const makeReducer = ({ state, action }) => {
if (action.type === LOG_OUT) {
return rootReducer({ action })
}
return rootReducer({ state, action })
}
Also, consider using terniary to solve simple cases:
const makeReducer = ({ state, action }) =>
rootReducer( action.type === LOG_OUT ? { action } : { state, action } )
Finally, there could be yet another approach using tagged sums and folds. Since I don't work with React and/or Redux, I don't know if you could go with this approach but I believe that it's still interesting that you discover this alternative solution:
const tag = Symbol ( 'tag' )
// TaggedSum
const Action = {
logout: value => ( { [tag]: 'logout', value } ),
login: value => ( { [tag]: 'login', value } )
}
const foldAction = matches => action => {
const actionTag = action[ tag ]
const match = matches [ actionTag ]
return match ( action.value )
}
const state = { x: 1 }
const LOG_IN = 1
const LOG_OUT = 2
const logout = Action.logout ( { action: LOG_OUT, state } )
const login = Action.login ( { action: LOG_IN, state } )
const rootReducer = args => console.log ( args )
// Pattern matching
const matchAction = {
logout: ( { state } ) => rootReducer( { state } ),
login: rootReducer
}
const foldAction_ = foldAction( matchAction )
foldAction_ ( logout )
foldAction_ ( login )
You can get rid of the duplication fairly easily:
const makeReducer = (state, action) =>
rootReducer((action.type === LOG_OUT ? undefined : state), action)
That is really neither more nor less functional than the original. But it does have the advantage of reducing duplication, and of dealing only with expressions and not statements, which is sometimes a concern of functional techniques.
But there is one way in which it is clearly not functional. There is a free variable in your code: LOG_OUT. I'm guessing from the ALL_CAPS that this is meant to be a constant. But the function doesn't know that. So this function is not actually referentially transparent. It's possible that between invocations with the same parameters, someone changes the value of LOG_OUT and you could get different results.
This makes the function harder to test. (You can't just supply it the necessary parameters; you also have to have the correct value of LOG_OUT in scope.) And it makes it much harder to reason about.
An alternative without this issue is
const makeReducer = (state, action, types) =>
rootReducer((action.type === types.LOG_OUT ? undefined : state), action)
If you want to use pointfree style syntax for your code, you could do something like:
const initialState = {
text: 'initial text'
}
const rootReducer = R.curry((state, action) => {
// setting initial state could be improved
state = state || initialState
// your root reducer logic here
return state;
})
// R.last is here to grab the action in [state, action]
const isAction = type => R.compose(R.equals(type), R.prop('type'), R.last)
// first makes (state, action) into [state, action]
// before running R.cond
const makeReducer = R.compose(R.cond([
[isAction('LOG_OUT'), R.compose(rootReducer(undefined), R.last)],
// "default" action
[R.T, R.apply(rootReducer)]
]), R.pair)
const loggedOutState = makeReducer(
{ text: 'latest text'},
{ type: 'LOG_OUT'}
)
console.log(loggedOutState)
// => { text: 'initial text' }
const nextState = makeReducer(
{ text: 'latest text'},
{ type: 'ANY_ACTION'}
)
console.log(nextState)
// => { text: 'latest text' }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
What's good about this solution is that you could easily extend makeReducer to handle more actions (since it's using R.cond -- which is like a switch statement).

Reselect Cannot read property 'get' of undefined

I am using reselect and react redux. I am trying to make a selector for a basic modal implementation.
my selector is
const selectModal = (state) => state.get('modal');
which throws the error of
Cannot read property 'get' of undefined
edit: It has been requested I show how I call select modal, though it should make no difference.
const mapStateToProps = createStructuredSelector({
isVisible: selectModalIsVisible(),
});
const mapDispatchToProps = {
hideModal,
showModal
};
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
I believe this means the modal state container is not being found
Perhaps I am setting up my reducer or store incorrectly. My reducer is
function modalReducer(state = initialState, action) {
switch (action.type) {
case HIDE_MODAL:
return state.set(
'isVisible', false);
case SHOW_MODAL:
return state.set(
'isVisible', true);
default:
return state;
}
}
which is combined with combine reducers into a glob
export default function createReducer(asyncReducers){
return combineReducers({
route: routeReducer,
auth: authReducer,
modal: modalReducer,
...asyncReducers
});
}
and then injected into my store
function configureStore(initialState = {}, history) {
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
]
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
);
store.runSaga = sagaMiddleware.run;
//store.close = () => store.dispatch(END)
store.runSaga(sagas);
store.asyncReducers = {};
return store;
}
var initialState = {}
const store = configureStore(fromJS(initialState), browserHistory);
The error within reselect is at lines 73/74 params = dependencies.map
var selector = function selector(state, props) {
for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
args[_key4 - 2] = arguments[_key4];
}
var params = dependencies.map(function (dependency) {
return dependency.apply(undefined, [state, props].concat(args));
});
return memoizedResultFunc.apply(undefined, _toConsumableArray(params));
};
So what am I doing wrong, do I need to do something with immutableJS differently to access the modal, or is my setup for the app incorrect? Thank you in advance for your feedback.
If you're using selectModal like you're using selectModalIsVisible, then your syntax is wrong. I'm pretty sure createStructuredSelector does not understand () => (state) => state.get('modal'). It would only accept (state) => state.get('modal')
Typically, my usages of createStructuredSelector will look like either
const getThing = (state, props) => state.things[props.thingId];
const getModal = state => state.get('modal');
const mapStateToProps = createStructuredSelector({
thing: getThing, // notice no parens
modal: getModal, // notice no parens
})
OR if I need selector factories:
// just pretend this selector was more complicated and needed memoization
const makeGetThing = () => createSelector(
state => state.things,
(state, props) => props.thingId,
(things, thingId) => things[thingId]);
const getModal = state => state.get('modal');
const makeMapStateToProps = () => createStructuredSelector({
thing: makeGetThing(), // yes parens
modal: getModal, // no parens
})

Resources