Possible to update multiple reducer by dispatching single redux action? - javascript

Disclaimer: this question is targeting specific package reduxsauce
Takes classic redux action, by dispatching a single action, it will flow thru all the reducer and if we want to update the state, we catch the type in each and every reducer as we see fit
loginPage.js
this.props.memberLogin({ name: 'john' }); //{ type: MEMBER_LOGIN, payload: { name: 'john' } }
LoginAction.js
const memberLogin = member => {
return { type: MEMBER_LOGIN, payload: member }
}
authReducer.js
const INITIAL_STATE = { isLoggedIn: false }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, isLoggedIn: true };
default: return state;
}
memberReducer.js
const INITIAL_STATE = { member: null }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, member: action.payload };
default: return state;
}
Wondering by using reduxsauce, can we still achieve something similar as demonstrated above? (dispatch single action and update both reducer)

Yes, you can.
I created this Snack example to help you, but the gist is that you have to configure your reducers to listen to the same action.
Kinda like so:
const reduceA = (state, action) => ({
...state,
a: action.value,
});
const reduceB = (state, action) => ({
...state,
b: action.value,
});
const { Types, Creators: actionCreators } = createActions({
testAction: ['value'],
});
const HANDLERS_A = {
[Types.TEST_ACTION]: reduceA,
};
const HANDLERS_B = {
[Types.TEST_ACTION]: reduceB,
};
In the example both reducers A and B have their state values altered by the same action testAction.

Related

How to save an object in redux?

I build an app in React with Redux and I try to send to my state an object and I try to save it in 'thisUser' but I don't know how to write that 'return' because mine doesn't work.
My Redux state:
const initialState = {
thisUser: {}
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: { ...state.thisUser, ...action.payload} } //the problem
default:
return state
}
}
Dispatch method:
dispatch({ type: "users/addUser", payload: new_user });
Can you tell me how to write that return, please?
If you want to append new user then why are you using object type. You should use Array Type thisUser.
const initialState = {
thisUser: []
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: [ ...state.thisUser,action.payload ] }
default:
return state
}
}
Or
If you want to save only single user object then change only that line in your code:
return { ...state, thisUser: action.payload }
It's better to use an array type for if you have a list of users .
If you have a case when you need to use an object just change the brackets [ ] on my code to curly braces { } .
const initialState = {
thisUser: [],
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/addUser':
return { ...state, thisUser: [ ...state.thisUser, ...action.payload]}
default:
return state
}
}

Reverse state in redux

I'm trying to implement sorting functionality in my notes app but have some problems with it. My sort function must just reverse an array of notes but it does not work.
I have a state with notes:
export const initialState = {
notes: [
{
id: nanoid(),
text: '',
date: '',
},
],
}
Action:
export const sortNoteAction = ([notes]) => ({
type: SORT_NOTE,
payload: [notes],
})
Reducer:
export default function notes(state = initialState, { type, payload }) {
switch (type) {
case SORT_NOTE: {
return {
...state,
notes: [payload].reverse(),
}
}
default:
return state
}
}
Action:
export const sortNoteAction = (notes=[]) => ({
type: SORT_NOTE,
payload: notes,
})
Reducer:
export default function notes(state = initialState, action) {
switch (action.type) {
case SORT_NOTE: {
return {
...state,
notes: action.payload.reverse(),
}
}
default:
return state
}
}
I think it doesn't work because you are essentially sorting a list with just one item. If you change [notes] to notes (2 occurrences) and [payload] to payload (1 occurrence) it will probably work

How can I use Redux to only update one instance of a component?

I'm trying to use Redux to update my Card Component to disable and change colors on click. Redux dispatches the action fine, but it updates all Cards not just the one that was clicked. Each Card has an object associated with it that hold the word and a value. The value is the className I want to use to change the color when clicked
Component
const Card = ({ wordObj, updateClass, isDisabled, cardClass }) => {
const showColor = (e) => {
updateClass(wordObj);
console.log(cardClass)
};
return (
<button
onClick={(e) => showColor()}
disabled={isDisabled}
className={cardClass}>
{wordObj.word}
</button>
);
};
const mapStateToProps = (state) => ({
cardClass: state.game.cardClass,
});
export default connect(mapStateToProps, { updateClass })(Card);
Action
export const updateClass = (obj) => (dispatch) => {
console.log(obj)
dispatch({
type: UPDATE_CARD,
payload: obj,
});
};
Reducer
const initialState = {
words: [],
cardClass: 'card',
isDisabled: false,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_WORDS: {
return {
...state,
words: payload,
};
}
case UPDATE_CARD:
return {
...state,
isDisabled: true,
cardClass: ['card', payload.value].join(' '),
};
default:
return state;
}
}```
All of your card components are consuming the same cardClass field in the state. When you modify it in this line:
cardClass: ['card', payload.value].join(' ')
All cards that are consuming this field have their classes updated. The same occurs to the isDisable field.
You need to create one object for each card in your state. Here is my implementation (was not tested):
const initialState = {
cards: []
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
// create a card object for each word
case SET_WORDS: {
return {
...state,
cards: payload.map(word => {
return { word: word, cardClass: "card", isDisabled: false }
})
};
}
case UPDATE_CARD:
// here i'm using the wordObj.word passed as payload
// to identify the card (i recommend to use an id field)
const cardIndex = state.cards.findIndex(card => card.word === payload.word);
// get the current card
const card = state.cards[cardIndex];
// create an updated card object obeying the immutability principle
const updatedCard = { ...card, isDisabled: true, cardClass: ['card', payload.value].join(' '), }
return {
...state,
cards: [
...state.cards.slice(0, cardIndex), // cards before
updatedCard,
...state.cards.slice(cardIndex + 1) // cards after
]
};
default:
return state;
}
}
Your mapStateToProps selects a string, but said string changes on any updateClass and that causes all your cards to update, because the selection of state.game.cardClass produces a different value, which triggers a new render for the connected component.
Maybe what you want, is something that identifies the selection, i.e. an id for each card, and select with that id in the mapStateToProps to avoid reading the change, because what's happening right now is the following:
Card A[className="card A"] == after dispatch ==> mapStateToProps => [className="card B"]
Card B[className="card A"] => dispatch('B') => mapStateToProps => [className="card B"]
B is updating the state of both A and B, and that's why the extra render occurs

React-Redux: How to change the initial state in Reducer function

I am working on a React application and I am using Redux to store the state. I have the following code:
menu.reducer.js:
import { GET_MENU } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return [ ...action.payload ];
default:
return state;
}
}
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { GET_MENU } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
In the above Reducer, the initial state is an empty array. Dispatching the GET_MENU action, changes the initial state so that it contains an array of menu items instead.
The array that is fetched in the GET_MENU action is of the following:
However I want my initial state to be like the following:
const INITIAL_STATE = {
menuArray = [],
isSending = false
}
In the GET_MENU case in the reducer code, I am not sure what the correct syntax is to use in order to assign the menuArray property in the state to the array that is returned from the GET_MENU action.
Any insights are appreciated.
The state is simply a JavaScript value. If you want it to be an object with two properties, this isn't the right syntax:
const INITIAL_STATE = {
menuArray = [],
isSending = false
}
This is:
const INITIAL_STATE = {
menuArray: [],
isSending: false
}
Your reducer will now need to also return objects. You'll want to return a new object each time. Here's how you can do your reducer, specifically:
import { GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return { ...state, menuArray: [...action.payload] };
default:
return state;
}
}
This says "create an object comprised of all the properties of the previous state but with the menuArray property set to the payload."
import { GET_MENU } from './menu.types';
const initialState= {
menuArray: [],
isSending: false
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_MENU:
return {...state, menuArray: action.payload};
default:
return state;
}
}
import { GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return {
...state,
menuArray: action.payload
};
default:
return state;
}
}

Update redux state object property

How do I update the property of an object in redux and preserve the rest of the object..
const appReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOGIN_SUCCESS':
return Object.assign({}, state, {
user: { loggedIn: true, level: 'default' },
});
case 'UPDATE_PACKAGE': {
// add a new value for the user.level which would be in action.level
return { ...state, level: action.level };
}
default:
return state;
}
};
So I would expect the UPDATE_PACKAGE to change the contents of the redux store level property... but its unchanged...
So it seems like you're setting level on the root state instead of the user object.
This should fix it:
case "UPDATE_PACKAGE": {
return { ...state, user: { ...state.user, level: action.level } };
}

Categories

Resources