Reusing reducer logic, action name is undefined - javascript

I'm looking at this documentation page: https://redux.js.org/recipes/structuring-reducers/reusing-reducer-logic on redux. I'm trying to implement something like this:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
However I don't understand how to get the name from action. And this code is not working on my project as const { name } = action is returning undefined.
What I'm I missing? Is this example complete? How can I fix this?

Related

Mutating Redux Store Array of Objects in ReactJS

My store consists of an array of objects as such:
const INIT_STATE = {
users:[],
contacts : []
};
And i am attempting to change mutate the store array like this:
const App = (state = INIT_STATE, action) => {
switch (action.type) {
case BLOCK_CONTACT:
state.contacts.map((contact, index) => {
if (contact._id === action.payload) {
state.contacts[index].status = "blocked;
return {
...state
};
}
return {
...state
};
})
return {
...state
};
}
}
But my store value does not change.
When i log the state value after the if statement, i get the correct state values but my state is not being updated. I think it might have something to do with my return statement but at this point i have tried different variations with no luck.
Any help will be appreciated.
Also if your app is in the start of the way, you can read this article about jotai state management (Jotai)
I figured it out. Incase anyone else that might need help on this, here is the code...
const App = (state = INIT_STATE, action) => {
switch (action.type) {
case BLOCK_CONTACT:
var list = state.contacts.map(contact => {
var blockList = {
...contact
};
if (contact._id === action.payload) {
blockList.status = 1;
}
return blockList;
})
return {
...state,
contacts: list
};
}
}

Store.Dispatch() Resetting Redux Store

I dispatch(action()) to trigger an action from outside my react component. It is working correctly in that it is triggering my action and updating the new item in my store. The problem is that it seems to be completely resetting everything else in my store, which to me at least makes it more of a problem than its worth.
Worth noting: I am using next.js.
Here is a basic idea of my flow:
I have a utils folder with a service where I am dispatching this action from:
import store from './store';
store.dispatch(myAction());
I have my actions
export const myAction = () => ({
type: HELP_ME,
payload: true,
});
const initialState = {
reducerVar: fase,
otherExistingVar: {},
otherExistingVarTwo: null,
otherExistingThree:null,
otherExistingVarFour: false,
};
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME: {
const reducerVar = action.payload;
}
default: {
return state;
}
}
};
export default myReducer;
I am not sure if I am misusing store.dispatch() because I dont see why anyone would use this technique if it completely wipes out the existing data in the store. Or is there a better way to trigger this simple action from outside my component.
Basically I want to dispatch this action without completely wiping out my store just like I would dispatch the action if I were in a component.
Thank You!
you should return the state with value in reducer like this.
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME: {
return {...state, reducerVar : action.payload}
}
default: {
return state;
}
}
};
What you are trying to do is fine. But your reducer must return the whole state with your change done by the action like below.
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME:
const reducerVar = action.payload;
return {...state, reducerVar }
default:
return state;
}
};

Is it possible to merge two reducers into one?

I would like to merge two reducers, the first being created as a generic one and the second one would be more specific to it's own state. Both these reducers would not handle the same cases. Merging these would only result in default case being duplicated, the default case always returning the default state anyways. This would help as I would only test the generic one once.
In case you were thinking about reduceReducers or combineReducers, that would not work since I have many "special" reducers with every one of them having the same action type to handle and all of those reducers have a different part of the state to modify.
const initialState = {
byId : {},
ids: []
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
I want to isolate the following cases : INITIALIZE and RESET in a generic switch/case function or a generic reducer, so I would only have to test those cases once and not in every reducer. There would be more generic cases in the future, that's why I want to avoid repetition.
This is the expected result :
const genericReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
default:
return state
}
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
const finalCatsReducer = mergeReducers(catsReducer, genericReducer)
const finalDogsReducer = mergeReducers(dogsReducer, genericReducer)
I could imagine using the following however I want to say that would have a similar effect to just putting them all flat. The only added benefit I can see is that the specific switch cases won't be verified unless the general cases fail.
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default: //specificSwitch
switch (type) {
case 2:
do something y
default:
return z
}
}
}
The simplest solution is to wrap into the upper method:
const combinedSwitch = type => {
const result = genericSwitch(type);
return result === z ? specificSwitch(type) : result;
}
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default:
return z
}
}
const specificSwitch = type => {
switch (type) {
case 2:
do something y
default:
return z
}
}

Redux doesn't re-render the components

I have a component which takes data from mapStateToProps() method. Component's code is:
handleClick = () => {
if (this.props.data.status) {
this.props.changeIpStatus(index, !this.props.data.status);
} else {
this.props.changeIpStatus(index, !this.props.data.status);
}
}
render() {
if (this.props.data.status) {
this.switchClasses = `switcher blocked`;
this.dotClasses = `dot blocked`;
} else {
this.switchClasses = `switcher`;
this.dotClasses = `dot`;
}
return (
<div className="block">
<div onClick={this.handleClick} className={this.switchClasses}>
<div className={this.dotClasses}></div>
</div>
</div>
)
}
}
My Redux connection looks like:
const mapStateToProps = (state) => ({
data: state.ipTable.data.clicks,
})
const mapDispatchToProps = (dispatch) => {
return {
changeIpStatus: (index, status) => {
return dispatch(changeIpStatus(index, status));
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BlockSwitcher)
When I click switcher it should re-render because the data is changed. I see that the data is changed through my console log. But it doesn't invoke re-render. Why? My component have mapStateToProps with data that changing and action import is correct (checked).
UPDATE:
This is my reducer:
const initialState = {
data: {}
}
const ipReducer = (state = initialState, action) => {
switch (action.type) {
case `SET_CLICKS`:
return {
...state,
data: action.data
}
case `CHANGE_IP_STATUS`:
let newState = Object.assign({}, state);
newState.data.clicks[action.index].status = action.status;
return newState;
default: return state;
}
}
export default ipReducer;
You can use JSON.parse(JSON.stringify(...)) method but be aware of that if your state includes a non-serializable property then you lose it.
Here is an alternative approach. You can see this method more frequently.
// map the clicks, if index match return the new one with the new status
// if the index does not match just return the current click
const newClicks = state.data.clicks.map((click, index) => {
if (index !== action.index) return click;
return { ...click, status: action.status };
});
// Here, set your new state with your new clicks without mutating the original one
const newState = { ...state, data: { ...state.data, clicks: newClicks } };
return newState;
The second alternative would be like that. Without mapping all the clicks we can use Object.assign for the clicks mutation.
const newClicks = Object.assign([], state.data.clicks, {
[action.index]: { ...state.data.clicks[action.index], status: action.status }
});
const newState = { ...state, data: { ...state.data, clicks: newClicks } };
return newState;
The problem was with deep copy of an object. In JavaScrip for copying object without any reference between them we have to use, for example:
let newState = JSON.parse(JSON.stringify(state));
not this:
let newState = Object.assign({}, state); // <-- this is do not return a standalone new object. Do not use it in your reducer.
Thanks to #kind user!
P.S This is an article with examples why Object.assign() do not work in this case.

Understanding Redux and State

I've taken two courses, treehouse and one on udemy, on react/redux and just when I think to myself "hey you got this, let's do a little practice" I run into some huge bug I can't seem to diagnose.
What I'm trying to do here sounds very simple, and in plain javascript it works. My state is an empty object state = {} and when my action is called, it creates an array inside of state noteName. So at the end of the day state should look like state = { noteName: [ ...state.noteName, action.payload]}.
When I console.log(this.props.inputvalue) it will return whatever is in the input element. I thought I understood objects because that consolelog should return the array noteName and not the actual value, correct?
Code
actions/index.js
export const INPUT_VALUE = 'INPUT_VALUE';
export function addNoteAction(text) {
return {
type: INPUT_VALUE,
payload: text
}
}
reducers/reducer_inputvalue.js
import { INPUT_VALUE } from '../actions';
// state is initialized as an empty object here
export default function(state = {}, action) {
switch (action.type) {
case INPUT_VALUE:
state.noteName = [];
// this SHOULD create an array that concats action.payload with
// whatever is already inside of state.name
return state.noteName = [...state.noteName, action.payload];
default:
return state;
}
}
noteitems.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class NoteItems extends Component {
render() {
return (
<ul>
{
this.props.inputvalue.noteName
?
this.props.inputvalue.noteName.map((note, index) => {
// this should iterate through noteName but returns undefined
return <li key={index}>{note}</li>;
})
:
<li>Nothing here</li>
}
</ul>
);
}
}
function mapStateToProps(state) {
return {
inputvalue: state.inputvalue
}
}
export default connect(mapStateToProps)(NoteItems);
This is happening because every time the action INPUT_VALUE is dispatched, you are resetting noteName. The main principle of redux is to not modify the state, but creating a new one based on the current. In your case:
const initialState = {
noteName: []
};
export default function(state = initialState, action) {
switch (action.type) {
case INPUT_VALUE: return {
noteName: [...state.noteName, action.payload]
};
default: return state;
}
}
You are overwriting state.noteName in the first line of your switch case.
switch (action.type) {
case INPUT_VALUE:
state.noteName = [];
In Redux, the point is to never overwrite a value, but to return a new value that might be a brand-new value, might be a value that is based on the old value (but still a new value... not overwriting the old), or it might be returning the old value (completely unmodified).
const counterReducer = (counter, action) => {
const options = {
[COUNTER_INCREMENT]: (counter, action) =>
({ value: counter.value + 1 }),
[COUNTER_DECREMENT]: (counter, action) =>
({ value: counter.value - 1 }),
[COUNTER_RESET]: (counter, action) =>
({ value: 0 })
};
const strategy = options[action.type];
return strategy ? strategy(counter, action) : counter;
};
At no point in that example am I modifying a value on counter. Everything is treated as read-only.

Categories

Resources